Browse Source

Merge branch 'master' of git://github.com/ckolivas/cgminer.git

Conflicts:
	cgminer.c
Paul Sheppard 13 years ago
parent
commit
baa480c137
7 changed files with 364 additions and 78 deletions
  1. 38 4
      API-README
  2. 2 0
      README
  3. 23 9
      adl.c
  4. 267 55
      api.c
  5. 17 2
      cgminer.c
  6. 2 0
      miner.h
  7. 15 8
      util.c

+ 38 - 4
API-README

@@ -19,8 +19,9 @@ IP addresses are automatically padded with extra '.0's as needed
 Without a /prefix is the same as specifying /32
 Without a /prefix is the same as specifying /32
 0/0 means all IP addresses.
 0/0 means all IP addresses.
 The 'W:' on the front gives that address/subnet privileged access to commands
 The 'W:' on the front gives that address/subnet privileged access to commands
-that modify cgminer.
+that modify cgminer (thus all API commands)
 Without it those commands return an access denied status.
 Without it those commands return an access denied status.
+See --api-groups below to define other groups like W:
 Privileged access is checked in the order the IP addresses were supplied to
 Privileged access is checked in the order the IP addresses were supplied to
 "--api-allow"
 "--api-allow"
 The first match determines the privilege level.
 The first match determines the privilege level.
@@ -28,6 +29,26 @@ Using the "--api-allow" option overides the "--api-network" option if they
 are both specified
 are both specified
 With "--api-allow", 127.0.0.1 is not by default given access unless specified
 With "--api-allow", 127.0.0.1 is not by default given access unless specified
 
 
+More groups (like the privileged group W:) can be defined using the
+--api-groups command
+Valid groups are only the letters A-Z (except R & W are predefined) and are
+not case sensitive
+The R: group is the same as not privileged access
+The W: group is (as stated) privileged access (thus all API commands)
+To give an IP address/subnet access to a group you use the group letter
+in front of the IP address instead of W: e.g. P:192.168.0/32
+An IP address/subnet can only be a member of one group
+A sample API group would be:
+ --api-groups P:switchpool:enablepool:addpool:disablepool:removepool:*
+This would create a group 'P' that can do all current pool commands and all
+non-priviliged commands - the '*' means all non-priviledged commands
+Without the '*' the group would only have access to the pool commands
+Defining multiple groups example:
+ --api-groups Q:quit:restart:*,S:save
+This would define 2 groups:
+ Q: that can 'quit' and 'restart' as well as all non-priviledged commands
+ S: that can only 'save' and no other commands
+
 The RPC API request can be either simple text or JSON.
 The RPC API request can be either simple text or JSON.
 
 
 If the request is JSON (starts with '{'), it will reply with a JSON formatted
 If the request is JSON (starts with '{'), it will reply with a JSON formatted
@@ -86,8 +107,8 @@ The list of requests - a (*) means it requires privileged access - and replies a
 
 
  Request       Reply Section  Details
  Request       Reply Section  Details
  -------       -------------  -------
  -------       -------------  -------
- version       VERSION        CGMiner=cgminer version
-                              API=API version
+ version       VERSION        CGMiner=cgminer, version
+                              API=API| version
 
 
  config        CONFIG         Some miner configuration information:
  config        CONFIG         Some miner configuration information:
                               GPU Count=N, <- the number of GPUs
                               GPU Count=N, <- the number of GPUs
@@ -243,6 +264,9 @@ The list of requests - a (*) means it requires privileged access - and replies a
                               Device drivers are also able to add stats to the
                               Device drivers are also able to add stats to the
                               end of the details returned
                               end of the details returned
 
 
+ check|cmd     COMMAND        Exists=Y/N, <- 'cmd' exists in this version
+                              Access=Y/N| <- you have access to use 'cmd'
+
 When you enable, disable or restart a GPU or PGA, you will also get Thread messages
 When you enable, disable or restart a GPU or PGA, you will also get Thread messages
 in the cgminer status window
 in the cgminer status window
 
 
@@ -285,7 +309,17 @@ miner.php - an example web page to access the API
 Feature Changelog for external applications using the API:
 Feature Changelog for external applications using the API:
 
 
 
 
-API V1.12
+API V1.13
+
+Added API commands:
+ 'check'
+
+Support was added to cgminer for API access groups with the --api-groups option
+It's 100% backwards compatible with previous --api-access commands
+
+----------
+
+API V1.12 (cgminer v2.4.3)
 
 
 Modified API commands:
 Modified API commands:
  'stats' - more pool stats added
  'stats' - more pool stats added

+ 2 - 0
README

@@ -119,6 +119,8 @@ Options for both config file and command line:
                     This overrides --api-network and you must specify 127.0.0.1 if it is required
                     This overrides --api-network and you must specify 127.0.0.1 if it is required
                     W: in front of the IP address gives that address privileged access to all api commands
                     W: in front of the IP address gives that address privileged access to all api commands
 --api-description   Description placed in the API status header (default: cgminer version)
 --api-description   Description placed in the API status header (default: cgminer version)
+--api-groups        API one letter groups G:cmd:cmd[,P:cmd:*...]
+                    See API-README for usage
 --api-listen        Listen for API requests (default: disabled)
 --api-listen        Listen for API requests (default: disabled)
                     By default any command that does not just display data returns access denied
                     By default any command that does not just display data returns access denied
                     See --api-allow to overcome this
                     See --api-allow to overcome this

+ 23 - 9
adl.c

@@ -13,6 +13,7 @@
 
 
 #include <stdio.h>
 #include <stdio.h>
 #include <string.h>
 #include <string.h>
+#include <math.h>
 
 
 #ifdef HAVE_CURSES
 #ifdef HAVE_CURSES
 #include <curses.h>
 #include <curses.h>
@@ -692,7 +693,11 @@ int gpu_fanpercent(int gpu)
 	unlock_adl();
 	unlock_adl();
 	if (unlikely(ga->has_fanspeed && ret == -1)) {
 	if (unlikely(ga->has_fanspeed && ret == -1)) {
 		applog(LOG_WARNING, "GPU %d stopped reporting fanspeed due to driver corruption", gpu);
 		applog(LOG_WARNING, "GPU %d stopped reporting fanspeed due to driver corruption", gpu);
-		applog(LOG_WARNING, "You will need to start cgminer from scratch to correct this");
+		if (opt_restart) {
+			applog(LOG_WARNING, "Restart enabled, will attempt to restart cgminer");
+			applog(LOG_WARNING, "You can disable this with the --no-restart option");
+			app_restart();
+		}
 		applog(LOG_WARNING, "Disabling fanspeed monitoring on this device");
 		applog(LOG_WARNING, "Disabling fanspeed monitoring on this device");
 		ga->has_fanspeed = false;
 		ga->has_fanspeed = false;
 		if (ga->twin) {
 		if (ga->twin) {
@@ -1020,6 +1025,7 @@ static int set_powertune(int gpu, int iPercentage)
 static bool fan_autotune(int gpu, int temp, int fanpercent, int lasttemp, bool *fan_window)
 static bool fan_autotune(int gpu, int temp, int fanpercent, int lasttemp, bool *fan_window)
 {
 {
 	struct cgpu_info *cgpu = &gpus[gpu];
 	struct cgpu_info *cgpu = &gpus[gpu];
+	int tdiff = round(temp - lasttemp);
 	struct gpu_adl *ga = &cgpu->adl;
 	struct gpu_adl *ga = &cgpu->adl;
 	int top = gpus[gpu].gpu_fan;
 	int top = gpus[gpu].gpu_fan;
 	int bot = gpus[gpu].min_fan;
 	int bot = gpus[gpu].min_fan;
@@ -1034,7 +1040,7 @@ static bool fan_autotune(int gpu, int temp, int fanpercent, int lasttemp, bool *
 		cgpu->device_last_not_well = time(NULL);
 		cgpu->device_last_not_well = time(NULL);
 		cgpu->device_not_well_reason = REASON_DEV_OVER_HEAT;
 		cgpu->device_not_well_reason = REASON_DEV_OVER_HEAT;
 		cgpu->dev_over_heat_count++;
 		cgpu->dev_over_heat_count++;
-	} else if (temp > ga->targettemp && fanpercent < top && temp >= lasttemp) {
+	} else if (temp > ga->targettemp && fanpercent < top && tdiff >= 0) {
 		applog(LOG_DEBUG, "Temperature over target, increasing fanspeed");
 		applog(LOG_DEBUG, "Temperature over target, increasing fanspeed");
 		if (temp > ga->targettemp + opt_hysteresis)
 		if (temp > ga->targettemp + opt_hysteresis)
 			newpercent = ga->targetfan + 10;
 			newpercent = ga->targetfan + 10;
@@ -1042,18 +1048,26 @@ static bool fan_autotune(int gpu, int temp, int fanpercent, int lasttemp, bool *
 			newpercent = ga->targetfan + 5;
 			newpercent = ga->targetfan + 5;
 		if (newpercent > top)
 		if (newpercent > top)
 			newpercent = top;
 			newpercent = top;
-	} else if (fanpercent > bot && temp < ga->targettemp - opt_hysteresis && temp <= lasttemp) {
-		applog(LOG_DEBUG, "Temperature %d degrees below target, decreasing fanspeed", opt_hysteresis);
-		newpercent = ga->targetfan - 1;
+	} else if (fanpercent > bot && temp < ga->targettemp - opt_hysteresis) {
+		/* Detect large swings of 5 degrees or more and change fan by
+		 * a proportion more */
+		if (tdiff <= 0) {
+			applog(LOG_DEBUG, "Temperature %d degrees below target, decreasing fanspeed", opt_hysteresis);
+			newpercent = ga->targetfan - 1 + tdiff / 5;
+		} else if (tdiff >= 5) {
+			applog(LOG_DEBUG, "Temperature climbed %d while below target, increasing fanspeed", tdiff);
+			newpercent = ga->targetfan + tdiff / 5;
+		}
 	} else {
 	} else {
+
 		/* We're in the optimal range, make minor adjustments if the
 		/* We're in the optimal range, make minor adjustments if the
 		 * temp is still drifting */
 		 * temp is still drifting */
-		if (fanpercent > bot && temp < lasttemp && lasttemp < ga->targettemp) {
+		if (fanpercent > bot && tdiff < 0 && lasttemp < ga->targettemp) {
 			applog(LOG_DEBUG, "Temperature dropping while in target range, decreasing fanspeed");
 			applog(LOG_DEBUG, "Temperature dropping while in target range, decreasing fanspeed");
-			newpercent = ga->targetfan - 1;
-		} else if (fanpercent < top && temp > lasttemp && temp > ga->targettemp - opt_hysteresis) {
+			newpercent = ga->targetfan + tdiff;
+		} else if (fanpercent < top && tdiff > 0 && temp > ga->targettemp - opt_hysteresis) {
 			applog(LOG_DEBUG, "Temperature rising while in target range, increasing fanspeed");
 			applog(LOG_DEBUG, "Temperature rising while in target range, increasing fanspeed");
-			newpercent = ga->targetfan + 1;
+			newpercent = ga->targetfan + tdiff;
 		}
 		}
 	}
 	}
 
 

+ 267 - 55
api.c

@@ -158,6 +158,7 @@ static char *msg_buffer = NULL;
 static SOCKETTYPE sock = INVSOCK;
 static SOCKETTYPE sock = INVSOCK;
 
 
 static const char *UNAVAILABLE = " - API will not be available";
 static const char *UNAVAILABLE = " - API will not be available";
+static const char *INVAPIGROUPS = "Invalid --api-groups parameter";
 
 
 static const char *BLANK = "";
 static const char *BLANK = "";
 static const char *COMMA = ",";
 static const char *COMMA = ",";
@@ -165,7 +166,7 @@ static const char SEPARATOR = '|';
 #define SEPSTR "|"
 #define SEPSTR "|"
 static const char GPUSEP = ',';
 static const char GPUSEP = ',';
 
 
-static const char *APIVERSION = "1.12";
+static const char *APIVERSION = "1.13";
 static const char *DEAD = "Dead";
 static const char *DEAD = "Dead";
 static const char *SICK = "Sick";
 static const char *SICK = "Sick";
 static const char *NOSTART = "NoStart";
 static const char *NOSTART = "NoStart";
@@ -243,6 +244,7 @@ static const char *OSINFO =
 #define _BYE		"BYE"
 #define _BYE		"BYE"
 #define _RESTART	"RESTART"
 #define _RESTART	"RESTART"
 #define _MINESTATS	"STATS"
 #define _MINESTATS	"STATS"
+#define _CHECK		"CHECK"
 
 
 static const char ISJSON = '{';
 static const char ISJSON = '{';
 #define JSON0		"{"
 #define JSON0		"{"
@@ -277,6 +279,7 @@ static const char ISJSON = '{';
 #define JSON_RESTART	JSON1 _RESTART JSON1
 #define JSON_RESTART	JSON1 _RESTART JSON1
 #define JSON_CLOSE	JSON3
 #define JSON_CLOSE	JSON3
 #define JSON_MINESTATS	JSON1 _MINESTATS JSON2
 #define JSON_MINESTATS	JSON1 _MINESTATS JSON2
+#define JSON_CHECK	JSON1 _CHECK JSON2
 #define JSON_END	JSON4
 #define JSON_END	JSON4
 
 
 static const char *JSON_COMMAND = "command";
 static const char *JSON_COMMAND = "command";
@@ -364,6 +367,8 @@ static const char *JSON_PARAMETER = "parameter";
 #define MSG_REMPOOL 68
 #define MSG_REMPOOL 68
 #define MSG_DEVDETAILS 69
 #define MSG_DEVDETAILS 69
 #define MSG_MINESTATS 70
 #define MSG_MINESTATS 70
+#define MSG_MISCHK 71
+#define MSG_CHECK 72
 
 
 enum code_severity {
 enum code_severity {
 	SEVERITY_ERR,
 	SEVERITY_ERR,
@@ -507,6 +512,8 @@ struct CODES {
  { SEVERITY_SUCC,  MSG_NOTIFY,	PARAM_NONE,	"Notify" },
  { SEVERITY_SUCC,  MSG_NOTIFY,	PARAM_NONE,	"Notify" },
  { SEVERITY_SUCC,  MSG_DEVDETAILS,PARAM_NONE,	"Device Details" },
  { SEVERITY_SUCC,  MSG_DEVDETAILS,PARAM_NONE,	"Device Details" },
  { SEVERITY_SUCC,  MSG_MINESTATS,PARAM_NONE,	"CGMiner stats" },
  { SEVERITY_SUCC,  MSG_MINESTATS,PARAM_NONE,	"CGMiner stats" },
+ { SEVERITY_ERR,   MSG_MISCHK,	PARAM_NONE,	"Missing check cmd" },
+ { SEVERITY_SUCC,  MSG_CHECK,	PARAM_NONE,	"Check command" },
  { SEVERITY_FAIL, 0, 0, NULL }
  { SEVERITY_FAIL, 0, 0, NULL }
 };
 };
 
 
@@ -525,9 +532,23 @@ static time_t when = 0;	// when the request occurred
 struct IP4ACCESS {
 struct IP4ACCESS {
 	in_addr_t ip;
 	in_addr_t ip;
 	in_addr_t mask;
 	in_addr_t mask;
-	bool writemode;
+	char group;
 };
 };
 
 
+#define GROUP(g) (toupper(g))
+#define PRIVGROUP GROUP('W')
+#define NOPRIVGROUP GROUP('R')
+#define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP)
+#define GROUPOFFSET(g) (GROUP(g) - GROUP('A'))
+#define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z'))
+#define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands)
+#define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL)
+
+struct APIGROUPS {
+	// This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search
+	char *commands;
+} apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs)
+
 static struct IP4ACCESS *ipaccess = NULL;
 static struct IP4ACCESS *ipaccess = NULL;
 static int ips = 0;
 static int ips = 0;
 
 
@@ -796,7 +817,7 @@ static char *message(int messageid, int paramid, char *param2, bool isjson)
 	return msg_buffer;
 	return msg_buffer;
 }
 }
 
 
-static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	sprintf(io_buffer, isjson
 	sprintf(io_buffer, isjson
 		? "%s," JSON_VERSION "{\"CGMiner\":\"%s\",\"API\":\"%s\"}" JSON_CLOSE
 		? "%s," JSON_VERSION "{\"CGMiner\":\"%s\",\"API\":\"%s\"}" JSON_CLOSE
@@ -805,7 +826,7 @@ static void apiversion(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		VERSION, APIVERSION);
 		VERSION, APIVERSION);
 }
 }
 
 
-static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void minerconfig(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	int gpucount = 0;
 	int gpucount = 0;
@@ -1003,7 +1024,7 @@ static void cpustatus(int cpu, bool isjson)
 }
 }
 #endif
 #endif
 
 
-static void devstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void devstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int devcount = 0;
 	int devcount = 0;
 	int numgpu = 0;
 	int numgpu = 0;
@@ -1069,7 +1090,7 @@ static void devstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, b
 }
 }
 
 
 #ifdef HAVE_OPENCL
 #ifdef HAVE_OPENCL
-static void gpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int id;
 	int id;
 
 
@@ -1103,7 +1124,7 @@ static void gpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 }
 }
 #endif
 #endif
 #ifdef HAVE_AN_FPGA
 #ifdef HAVE_AN_FPGA
-static void pgadev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void pgadev(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int numpga = numpgas();
 	int numpga = numpgas();
 	int id;
 	int id;
@@ -1137,7 +1158,7 @@ static void pgadev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 		strcat(io_buffer, JSON_CLOSE);
 		strcat(io_buffer, JSON_CLOSE);
 }
 }
 
 
-static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int numpga = numpgas();
 	int numpga = numpgas();
 	struct thr_info *thr;
 	struct thr_info *thr;
@@ -1193,7 +1214,7 @@ static void pgaenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_PGAENA, id, NULL, isjson));
 	strcpy(io_buffer, message(MSG_PGAENA, id, NULL, isjson));
 }
 }
 
 
-static void pgadisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void pgadisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int numpga = numpgas();
 	int numpga = numpgas();
 	int id;
 	int id;
@@ -1234,7 +1255,7 @@ static void pgadisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 #endif
 #endif
 
 
 #ifdef WANT_CPUMINE
 #ifdef WANT_CPUMINE
-static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int id;
 	int id;
 
 
@@ -1268,7 +1289,7 @@ static void cpudev(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 }
 }
 #endif
 #endif
 
 
-static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	char *status, *lp;
 	char *status, *lp;
@@ -1345,7 +1366,7 @@ static void poolstatus(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		strcat(io_buffer, JSON_CLOSE);
 		strcat(io_buffer, JSON_CLOSE);
 }
 }
 
 
-static void summary(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void summary(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	double utility, mhs;
 	double utility, mhs;
 
 
@@ -1379,7 +1400,7 @@ static void summary(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, boo
 #endif
 #endif
 }
 }
 #ifdef HAVE_OPENCL
 #ifdef HAVE_OPENCL
-static void gpuenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpuenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	struct thr_info *thr;
 	struct thr_info *thr;
 	int gpu;
 	int gpu;
@@ -1425,7 +1446,7 @@ static void gpuenable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUREN, id, NULL, isjson));
 	strcpy(io_buffer, message(MSG_GPUREN, id, NULL, isjson));
 }
 }
 
 
-static void gpudisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpudisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int id;
 	int id;
 
 
@@ -1455,7 +1476,7 @@ static void gpudisable(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUDIS, id, NULL, isjson));
 	strcpy(io_buffer, message(MSG_GPUDIS, id, NULL, isjson));
 }
 }
 
 
-static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int id;
 	int id;
 
 
@@ -1480,7 +1501,7 @@ static void gpurestart(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUREI, id, NULL, isjson));
 	strcpy(io_buffer, message(MSG_GPUREI, id, NULL, isjson));
 }
 }
 #endif
 #endif
-static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	int numgpu = 0;
 	int numgpu = 0;
@@ -1499,8 +1520,7 @@ static void gpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
 	strcat(io_buffer, buf);
 	strcat(io_buffer, buf);
 }
 }
 
 
-
-static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	int count = 0;
 	int count = 0;
@@ -1519,7 +1539,7 @@ static void pgacount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
 	strcat(io_buffer, buf);
 	strcat(io_buffer, buf);
 }
 }
 
 
-static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	int count = 0;
 	int count = 0;
@@ -1538,7 +1558,7 @@ static void cpucount(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bo
 	strcat(io_buffer, buf);
 	strcat(io_buffer, buf);
 }
 }
 
 
-static void switchpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void switchpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	struct pool *pool;
 	struct pool *pool;
 	int id;
 	int id;
@@ -1619,7 +1639,7 @@ exitsama:
 	return false;
 	return false;
 }
 }
 
 
-static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char *url, *user, *pass;
 	char *url, *user, *pass;
 	char *ptr;
 	char *ptr;
@@ -1647,7 +1667,7 @@ static void addpool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	ptr = NULL;
 	ptr = NULL;
 }
 }
 
 
-static void enablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void enablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	struct pool *pool;
 	struct pool *pool;
 	int id;
 	int id;
@@ -1681,7 +1701,7 @@ static void enablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_ENAPOOL, id, NULL, isjson));
 	strcpy(io_buffer, message(MSG_ENAPOOL, id, NULL, isjson));
 }
 }
 
 
-static void disablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void disablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	struct pool *pool;
 	struct pool *pool;
 	int id;
 	int id;
@@ -1720,7 +1740,7 @@ static void disablepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_DISPOOL, id, NULL, isjson));
 	strcpy(io_buffer, message(MSG_DISPOOL, id, NULL, isjson));
 }
 }
 
 
-static void removepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void removepool(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	struct pool *pool;
 	struct pool *pool;
 	char *rpc_url;
 	char *rpc_url;
@@ -1806,7 +1826,7 @@ static bool splitgpuvalue(char *param, int *gpu, char **value, bool isjson)
 
 
 	return true;
 	return true;
 }
 }
-static void gpuintensity(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+static void gpuintensity(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	int id;
 	int id;
 	char *value;
 	char *value;
@@ -1835,7 +1855,7 @@ static void gpuintensity(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
 	strcpy(io_buffer, message(MSG_GPUINT, id, intensitystr, isjson));
 	strcpy(io_buffer, message(MSG_GPUINT, id, intensitystr, isjson));
 }
 }
 
 
-static void gpumem(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpumem(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 #ifdef HAVE_ADL
 #ifdef HAVE_ADL
 	int id;
 	int id;
@@ -1856,7 +1876,7 @@ static void gpumem(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
 #endif
 #endif
 }
 }
 
 
-static void gpuengine(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpuengine(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 #ifdef HAVE_ADL
 #ifdef HAVE_ADL
 	int id;
 	int id;
@@ -1877,7 +1897,7 @@ static void gpuengine(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, b
 #endif
 #endif
 }
 }
 
 
-static void gpufan(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpufan(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 #ifdef HAVE_ADL
 #ifdef HAVE_ADL
 	int id;
 	int id;
@@ -1898,7 +1918,7 @@ static void gpufan(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
 #endif
 #endif
 }
 }
 
 
-static void gpuvddc(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void gpuvddc(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 #ifdef HAVE_ADL
 #ifdef HAVE_ADL
 	int id;
 	int id;
@@ -1919,7 +1939,7 @@ static void gpuvddc(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, boo
 #endif
 #endif
 }
 }
 #endif
 #endif
-void doquit(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+void doquit(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	if (isjson)
 	if (isjson)
 		strcpy(io_buffer, JSON_START JSON_BYE);
 		strcpy(io_buffer, JSON_START JSON_BYE);
@@ -1930,7 +1950,7 @@ void doquit(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson
 	do_a_quit = true;
 	do_a_quit = true;
 }
 }
 
 
-void dorestart(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+void dorestart(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	if (isjson)
 	if (isjson)
 		strcpy(io_buffer, JSON_START JSON_RESTART);
 		strcpy(io_buffer, JSON_START JSON_RESTART);
@@ -1941,12 +1961,12 @@ void dorestart(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isj
 	do_a_restart = true;
 	do_a_restart = true;
 }
 }
 
 
-void privileged(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+void privileged(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	strcpy(io_buffer, message(MSG_ACCOK, 0, NULL, isjson));
 	strcpy(io_buffer, message(MSG_ACCOK, 0, NULL, isjson));
 }
 }
 
 
-void notifystatus(int device, struct cgpu_info *cgpu, bool isjson)
+void notifystatus(int device, struct cgpu_info *cgpu, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	char *reason;
 	char *reason;
@@ -2000,7 +2020,7 @@ void notifystatus(int device, struct cgpu_info *cgpu, bool isjson)
 	strcat(io_buffer, buf);
 	strcat(io_buffer, buf);
 }
 }
 
 
-static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, char group)
 {
 {
 	int i;
 	int i;
 
 
@@ -2017,13 +2037,13 @@ static void notify(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool
 	}
 	}
 
 
 	for (i = 0; i < total_devices; i++)
 	for (i = 0; i < total_devices; i++)
-		notifystatus(i, devices[i], isjson);
+		notifystatus(i, devices[i], isjson, group);
 
 
 	if (isjson)
 	if (isjson)
 		strcat(io_buffer, JSON_CLOSE);
 		strcat(io_buffer, JSON_CLOSE);
 }
 }
 
 
-static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char buf[TMPBUFSIZ];
 	char buf[TMPBUFSIZ];
 	struct cgpu_info *cgpu;
 	struct cgpu_info *cgpu;
@@ -2059,7 +2079,7 @@ static void devdetails(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		strcat(io_buffer, JSON_CLOSE);
 		strcat(io_buffer, JSON_CLOSE);
 }
 }
 
 
-void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson)
+void dosave(__maybe_unused SOCKETTYPE c, char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char filename[PATH_MAX];
 	char filename[PATH_MAX];
 	FILE *fcfg;
 	FILE *fcfg;
@@ -2135,7 +2155,8 @@ static int itemstats(int i, char *id, struct cgminer_stats *stats, struct cgmine
 
 
 	return i;
 	return i;
 }
 }
-static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson)
+
+static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param, bool isjson, __maybe_unused char group)
 {
 {
 	char extra[TMPBUFSIZ];
 	char extra[TMPBUFSIZ];
 	char id[20];
 	char id[20];
@@ -2174,10 +2195,12 @@ static void minerstats(__maybe_unused SOCKETTYPE c, __maybe_unused char *param,
 		strcat(io_buffer, JSON_CLOSE);
 		strcat(io_buffer, JSON_CLOSE);
 }
 }
 
 
+static void checkcommand(__maybe_unused SOCKETTYPE c, char *param, bool isjson, char group);
+
 struct CMDS {
 struct CMDS {
 	char *name;
 	char *name;
-	void (*func)(SOCKETTYPE, char *, bool);
-	bool requires_writemode;
+	void (*func)(SOCKETTYPE, char *, bool, char);
+	bool iswritemode;
 } cmds[] = {
 } cmds[] = {
 	{ "version",		apiversion,	false },
 	{ "version",		apiversion,	false },
 	{ "config",		minerconfig,	false },
 	{ "config",		minerconfig,	false },
@@ -2220,9 +2243,47 @@ struct CMDS {
 	{ "devdetails",		devdetails,	false },
 	{ "devdetails",		devdetails,	false },
 	{ "restart",		dorestart,	true },
 	{ "restart",		dorestart,	true },
 	{ "stats",		minerstats,	false },
 	{ "stats",		minerstats,	false },
+	{ "check",		checkcommand,	false },
 	{ NULL,			NULL,		false }
 	{ NULL,			NULL,		false }
 };
 };
 
 
+static void checkcommand(__maybe_unused SOCKETTYPE c, char *param, bool isjson, char group)
+{
+	char buf[TMPBUFSIZ];
+	char cmdbuf[100];
+	bool found, access;
+	int i;
+
+	if (param == NULL || *param == '\0') {
+		strcpy(io_buffer, message(MSG_MISCHK, 0, NULL, isjson));
+		return;
+	}
+
+	found = false;
+	access = false;
+	for (i = 0; cmds[i].name != NULL; i++) {
+		if (strcmp(cmds[i].name, param) == 0) {
+			found = true;
+
+			sprintf(cmdbuf, "|%s|", param);
+			if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
+				access = true;
+
+			break;
+		}
+	}
+
+	strcpy(io_buffer, message(MSG_CHECK, 0, NULL, isjson));
+
+	sprintf(buf, isjson
+		? "," JSON_CHECK "{\"Exists\":\"%s\",\"Access\":\"%s\"}" JSON_CLOSE
+		: _CHECK ",Exists=%s,Access=%s" SEPSTR,
+		found ? YES : NO,
+		access ? YES : NO);
+
+	strcat(io_buffer, buf);
+}
+
 static void send_result(SOCKETTYPE c, bool isjson)
 static void send_result(SOCKETTYPE c, bool isjson)
 {
 {
 	int n;
 	int n;
@@ -2277,7 +2338,153 @@ static void tidyup(__maybe_unused void *arg)
 }
 }
 
 
 /*
 /*
- * Interpret [R|W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option
+ * Interpret --api-groups G:cmd1:cmd2:cmd3,P:cmd4,*,...
+ */
+static void setup_groups()
+{
+	char *api_groups = opt_api_groups ? opt_api_groups : (char *)BLANK;
+	char *buf, *ptr, *next, *colon;
+	char group;
+	char commands[TMPBUFSIZ];
+	char cmdbuf[100];
+	char *cmd;
+	bool addstar, did;
+	int i;
+
+	buf = malloc(strlen(api_groups) + 1);
+	if (unlikely(!buf))
+		quit(1, "Failed to malloc ipgroups buf");
+
+	strcpy(buf, api_groups);
+
+	next = buf;
+	// for each group defined
+	while (next && *next) {
+		ptr = next;
+		next = strchr(ptr, ',');
+		if (next)
+			*(next++) = '\0';
+
+		// Validate the group
+		if (*(ptr+1) != ':') {
+			colon = strchr(ptr, ':');
+			if (colon)
+				*colon = '\0';
+			applog(LOG_WARNING, "API invalid group name '%s'", ptr);
+			quit(1, INVAPIGROUPS);
+		}
+
+		group = GROUP(*ptr);
+		if (!VALIDGROUP(group)) {
+			applog(LOG_WARNING, "API invalid group name '%c'", *ptr);
+			quit(1, INVAPIGROUPS);
+		}
+
+		if (group == PRIVGROUP) {
+			applog(LOG_WARNING, "API group name can't be '%c'", PRIVGROUP);
+			quit(1, INVAPIGROUPS);
+		}
+
+		if (group == NOPRIVGROUP) {
+			applog(LOG_WARNING, "API group name can't be '%c'", NOPRIVGROUP);
+			quit(1, INVAPIGROUPS);
+		}
+
+		if (apigroups[GROUPOFFSET(group)].commands != NULL) {
+			applog(LOG_WARNING, "API duplicate group name '%c'", *ptr);
+			quit(1, INVAPIGROUPS);
+		}
+
+		ptr += 2;
+
+		// Validate the command list (and handle '*')
+		cmd = &(commands[0]);
+		*(cmd++) = SEPARATOR;
+		*cmd = '\0';
+		addstar = false;
+		while (ptr && *ptr) {
+			colon = strchr(ptr, ':');
+			if (colon)
+				*(colon++) = '\0';
+
+			if (strcmp(ptr, "*") == 0)
+				addstar = true;
+			else {
+				did = false;
+				for (i = 0; cmds[i].name != NULL; i++) {
+					if (strcasecmp(ptr, cmds[i].name) == 0) {
+						did = true;
+						break;
+					}
+				}
+				if (did) {
+					// skip duplicates
+					sprintf(cmdbuf, "|%s|", cmds[i].name);
+					if (strstr(commands, cmdbuf) == NULL) {
+						strcpy(cmd, cmds[i].name);
+						cmd += strlen(cmds[i].name);
+						*(cmd++) = SEPARATOR;
+						*cmd = '\0';
+					}
+				} else {
+					applog(LOG_WARNING, "API unknown command '%s' in group '%c'", ptr, group);
+					quit(1, INVAPIGROUPS);
+				}
+			}
+
+			ptr = colon;
+		}
+
+		// * = allow all non-iswritemode commands
+		if (addstar) {
+			for (i = 0; cmds[i].name != NULL; i++) {
+				if (cmds[i].iswritemode == false) {
+					// skip duplicates
+					sprintf(cmdbuf, "|%s|", cmds[i].name);
+					if (strstr(commands, cmdbuf) == NULL) {
+						strcpy(cmd, cmds[i].name);
+						cmd += strlen(cmds[i].name);
+						*(cmd++) = SEPARATOR;
+						*cmd = '\0';
+					}
+				}
+			}
+		}
+
+		ptr = apigroups[GROUPOFFSET(group)].commands = malloc(strlen(commands) + 1);
+		if (unlikely(!ptr))
+			quit(1, "Failed to malloc group commands buf");
+
+		strcpy(ptr, commands);
+	}
+
+	// Now define R (NOPRIVGROUP) as all non-iswritemode commands
+	cmd = &(commands[0]);
+	*(cmd++) = SEPARATOR;
+	*cmd = '\0';
+	for (i = 0; cmds[i].name != NULL; i++) {
+		if (cmds[i].iswritemode == false) {
+			strcpy(cmd, cmds[i].name);
+			cmd += strlen(cmds[i].name);
+			*(cmd++) = SEPARATOR;
+			*cmd = '\0';
+		}
+	}
+
+	ptr = apigroups[GROUPOFFSET(NOPRIVGROUP)].commands = malloc(strlen(commands) + 1);
+	if (unlikely(!ptr))
+		quit(1, "Failed to malloc noprivgroup commands buf");
+
+	strcpy(ptr, commands);
+
+	// W (PRIVGROUP) is handled as a special case since it simply means all commands
+
+	free(buf);
+	return;
+}
+
+/*
+ * Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option
  *	special case of 0/0 allows /0 (means all IP addresses)
  *	special case of 0/0 allows /0 (means all IP addresses)
  */
  */
 #define ALLIP4 "0/0"
 #define ALLIP4 "0/0"
@@ -2288,7 +2495,7 @@ static void setup_ipaccess()
 {
 {
 	char *buf, *ptr, *comma, *slash, *dot;
 	char *buf, *ptr, *comma, *slash, *dot;
 	int ipcount, mask, octet, i;
 	int ipcount, mask, octet, i;
-	bool writemode;
+	char group;
 
 
 	buf = malloc(strlen(opt_api_allow) + 1);
 	buf = malloc(strlen(opt_api_allow) + 1);
 	if (unlikely(!buf))
 	if (unlikely(!buf))
@@ -2322,16 +2529,16 @@ static void setup_ipaccess()
 		if (comma)
 		if (comma)
 			*(comma++) = '\0';
 			*(comma++) = '\0';
 
 
-		writemode = false;
+		group = NOPRIVGROUP;
 
 
 		if (isalpha(*ptr) && *(ptr+1) == ':') {
 		if (isalpha(*ptr) && *(ptr+1) == ':') {
-			if (tolower(*ptr) == 'w')
-				writemode = true;
+			if (DEFINEDGROUP(*ptr))
+				group = GROUP(*ptr);
 
 
 			ptr += 2;
 			ptr += 2;
 		}
 		}
 
 
-		ipaccess[ips].writemode = writemode;
+		ipaccess[ips].group = group;
 
 
 		if (strcmp(ptr, ALLIP4) == 0)
 		if (strcmp(ptr, ALLIP4) == 0)
 			ipaccess[ips].ip = ipaccess[ips].mask = 0;
 			ipaccess[ips].ip = ipaccess[ips].mask = 0;
@@ -2421,10 +2628,11 @@ void api(int api_thr_id)
 	struct sockaddr_in serv;
 	struct sockaddr_in serv;
 	struct sockaddr_in cli;
 	struct sockaddr_in cli;
 	socklen_t clisiz;
 	socklen_t clisiz;
+	char cmdbuf[100];
 	char *cmd;
 	char *cmd;
 	char *param;
 	char *param;
 	bool addrok;
 	bool addrok;
-	bool writemode;
+	char group;
 	json_error_t json_err;
 	json_error_t json_err;
 	json_t *json_config;
 	json_t *json_config;
 	json_t *json_val;
 	json_t *json_val;
@@ -2437,14 +2645,13 @@ void api(int api_thr_id)
 	pthread_cleanup_push(tidyup, NULL);
 	pthread_cleanup_push(tidyup, NULL);
 	my_thr_id = api_thr_id;
 	my_thr_id = api_thr_id;
 
 
-	/* This should be done first to ensure curl has already called WSAStartup() in windows */
-	sleep(opt_log_interval);
-
 	if (!opt_api_listen) {
 	if (!opt_api_listen) {
 		applog(LOG_DEBUG, "API not running%s", UNAVAILABLE);
 		applog(LOG_DEBUG, "API not running%s", UNAVAILABLE);
 		return;
 		return;
 	}
 	}
 
 
+	setup_groups();
+
 	if (opt_api_allow) {
 	if (opt_api_allow) {
 		setup_ipaccess();
 		setup_ipaccess();
 
 
@@ -2454,6 +2661,10 @@ void api(int api_thr_id)
 		}
 		}
 	}
 	}
 
 
+	/* This should be done before curl in needed
+	 * to ensure curl has already called WSAStartup() in windows */
+	sleep(opt_log_interval);
+
 	sock = socket(AF_INET, SOCK_STREAM, 0);
 	sock = socket(AF_INET, SOCK_STREAM, 0);
 	if (sock == INVSOCK) {
 	if (sock == INVSOCK) {
 		applog(LOG_ERR, "API1 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
 		applog(LOG_ERR, "API1 initialisation failed (%s)%s", SOCKERRMSG, UNAVAILABLE);
@@ -2523,12 +2734,12 @@ void api(int api_thr_id)
 		connectaddr = inet_ntoa(cli.sin_addr);
 		connectaddr = inet_ntoa(cli.sin_addr);
 
 
 		addrok = false;
 		addrok = false;
-		writemode = false;
+		group = NOPRIVGROUP;
 		if (opt_api_allow) {
 		if (opt_api_allow) {
 			for (i = 0; i < ips; i++) {
 			for (i = 0; i < ips; i++) {
 				if ((cli.sin_addr.s_addr & ipaccess[i].mask) == ipaccess[i].ip) {
 				if ((cli.sin_addr.s_addr & ipaccess[i].mask) == ipaccess[i].ip) {
 					addrok = true;
 					addrok = true;
-					writemode = ipaccess[i].writemode;
+					group = ipaccess[i].group;
 					break;
 					break;
 				}
 				}
 			}
 			}
@@ -2622,12 +2833,13 @@ void api(int api_thr_id)
 				if (!did)
 				if (!did)
 					for (i = 0; cmds[i].name != NULL; i++) {
 					for (i = 0; cmds[i].name != NULL; i++) {
 						if (strcmp(cmd, cmds[i].name) == 0) {
 						if (strcmp(cmd, cmds[i].name) == 0) {
-							if (cmds[i].requires_writemode && !writemode) {
+							sprintf(cmdbuf, "|%s|", cmd);
+							if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
+								(cmds[i].func)(c, param, isjson, group);
+							else {
 								strcpy(io_buffer, message(MSG_ACCDENY, 0, cmds[i].name, isjson));
 								strcpy(io_buffer, message(MSG_ACCDENY, 0, cmds[i].name, isjson));
 								applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name);
 								applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name);
 							}
 							}
-							else
-								(cmds[i].func)(c, param, isjson);
 
 
 							send_result(c, isjson);
 							send_result(c, isjson);
 							did = true;
 							did = true;

+ 17 - 2
cgminer.c

@@ -100,6 +100,7 @@ int opt_scantime = 60;
 int opt_expiry = 120;
 int opt_expiry = 120;
 int opt_bench_algo = -1;
 int opt_bench_algo = -1;
 static const bool opt_time = true;
 static const bool opt_time = true;
+unsigned long long global_hashrate;
 
 
 #ifdef HAVE_OPENCL
 #ifdef HAVE_OPENCL
 int opt_dynamic_interval = 7;
 int opt_dynamic_interval = 7;
@@ -131,6 +132,7 @@ bool opt_autofan;
 bool opt_autoengine;
 bool opt_autoengine;
 bool opt_noadl;
 bool opt_noadl;
 char *opt_api_allow = NULL;
 char *opt_api_allow = NULL;
+char *opt_api_groups;
 char *opt_api_description = PACKAGE_STRING;
 char *opt_api_description = PACKAGE_STRING;
 int opt_api_port = 4028;
 int opt_api_port = 4028;
 bool opt_api_listen;
 bool opt_api_listen;
@@ -680,6 +682,13 @@ static char *set_api_allow(const char *arg)
 	return NULL;
 	return NULL;
 }
 }
 
 
+static char *set_api_groups(const char *arg)
+{
+	opt_set_charp(arg, &opt_api_groups);
+
+	return NULL;
+}
+
 static char *set_api_description(const char *arg)
 static char *set_api_description(const char *arg)
 {
 {
 	opt_set_charp(arg, &opt_api_description);
 	opt_set_charp(arg, &opt_api_description);
@@ -730,10 +739,13 @@ static struct opt_table opt_config_table[] = {
 #endif
 #endif
 	OPT_WITH_ARG("--api-allow",
 	OPT_WITH_ARG("--api-allow",
 		     set_api_allow, NULL, NULL,
 		     set_api_allow, NULL, NULL,
-		     "Allow API access only to the given list of IP[/Prefix] addresses[/subnets]"),
+		     "Allow API access only to the given list of [G:]IP[/Prefix] addresses[/subnets]"),
 	OPT_WITH_ARG("--api-description",
 	OPT_WITH_ARG("--api-description",
 		     set_api_description, NULL, NULL,
 		     set_api_description, NULL, NULL,
 		     "Description placed in the API status header, default: cgminer version"),
 		     "Description placed in the API status header, default: cgminer version"),
+	OPT_WITH_ARG("--api-groups",
+		     set_api_groups, NULL, NULL,
+		     "API one letter groups G:cmd:cmd[,P:cmd:*...] defining the cmds a groups can use"),
 	OPT_WITHOUT_ARG("--api-listen",
 	OPT_WITHOUT_ARG("--api-listen",
 			opt_set_bool, &opt_api_listen,
 			opt_set_bool, &opt_api_listen,
 			"Enable API, default: disabled"),
 			"Enable API, default: disabled"),
@@ -2872,6 +2884,8 @@ void write_config(FILE *fcfg)
 		fprintf(fcfg, ",\n\"api-allow\" : \"%s\"", opt_api_allow);
 		fprintf(fcfg, ",\n\"api-allow\" : \"%s\"", opt_api_allow);
 	if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
 	if (strcmp(opt_api_description, PACKAGE_STRING) != 0)
 		fprintf(fcfg, ",\n\"api-description\" : \"%s\"", opt_api_description);
 		fprintf(fcfg, ",\n\"api-description\" : \"%s\"", opt_api_description);
+	if (opt_api_groups)
+		fprintf(fcfg, ",\n\"api-groups\" : \"%s\"", opt_api_groups);
 	if (opt_icarus_timing)
 	if (opt_icarus_timing)
 		fprintf(fcfg, ",\n\"icarus-timing\" : \"%s\"", opt_icarus_timing);
 		fprintf(fcfg, ",\n\"icarus-timing\" : \"%s\"", opt_icarus_timing);
 	fputs("\n}", fcfg);
 	fputs("\n}", fcfg);
@@ -3387,6 +3401,7 @@ static void hashmeter(int thr_id, struct timeval *diff,
 
 
 	local_secs = (double)total_diff.tv_sec + ((double)total_diff.tv_usec / 1000000.0);
 	local_secs = (double)total_diff.tv_sec + ((double)total_diff.tv_usec / 1000000.0);
 	decay_time(&rolling, local_mhashes_done / local_secs);
 	decay_time(&rolling, local_mhashes_done / local_secs);
+	global_hashrate = roundl(rolling) * 1000000;
 
 
 	timeval_subtract(&total_diff, &total_tv_end, &total_tv_start);
 	timeval_subtract(&total_diff, &total_tv_end, &total_tv_start);
 	total_secs = (double)total_diff.tv_sec +
 	total_secs = (double)total_diff.tv_sec +
@@ -3649,7 +3664,7 @@ static inline bool should_roll(struct work *work)
 static inline bool can_roll(struct work *work)
 static inline bool can_roll(struct work *work)
 {
 {
 	return (work->pool && work->rolltime && !work->clone &&
 	return (work->pool && work->rolltime && !work->clone &&
-work->rolls < 7000 && !stale_work(work, false));
+		work->rolls < 7000 && !stale_work(work, false));
 }
 }
 
 
 static void roll_work(struct work *work)
 static void roll_work(struct work *work)

+ 2 - 0
miner.h

@@ -531,6 +531,7 @@ extern bool opt_autofan;
 extern bool opt_autoengine;
 extern bool opt_autoengine;
 extern bool use_curses;
 extern bool use_curses;
 extern char *opt_api_allow;
 extern char *opt_api_allow;
+extern char *opt_api_groups;
 extern char *opt_api_description;
 extern char *opt_api_description;
 extern int opt_api_port;
 extern int opt_api_port;
 extern bool opt_api_listen;
 extern bool opt_api_listen;
@@ -626,6 +627,7 @@ extern unsigned int local_work;
 extern unsigned int total_go, total_ro;
 extern unsigned int total_go, total_ro;
 extern const int opt_cutofftemp;
 extern const int opt_cutofftemp;
 extern int opt_log_interval;
 extern int opt_log_interval;
+extern unsigned long long global_hashrate;
 
 
 #ifdef HAVE_OPENCL
 #ifdef HAVE_OPENCL
 typedef struct {
 typedef struct {

+ 15 - 8
util.c

@@ -256,17 +256,17 @@ json_t *json_rpc_call(CURL *curl, const char *url,
 		      bool probe, bool longpoll, int *rolltime,
 		      bool probe, bool longpoll, int *rolltime,
 		      struct pool *pool, bool share)
 		      struct pool *pool, bool share)
 {
 {
-	json_t *val, *err_val, *res_val;
-	int rc;
-	struct data_buffer all_data = {NULL, 0};
-	struct upload_buffer upload_data;
-	json_error_t err;
-	struct curl_slist *headers = NULL;
-	char len_hdr[64], user_agent_hdr[128];
-	char curl_err_str[CURL_ERROR_SIZE];
+	char len_hdr[64], user_agent_hdr[128], *ghashrate;
 	long timeout = longpoll ? (60 * 60) : 60;
 	long timeout = longpoll ? (60 * 60) : 60;
+	struct data_buffer all_data = {NULL, 0};
 	struct header_info hi = {NULL, 0, NULL};
 	struct header_info hi = {NULL, 0, NULL};
+	char curl_err_str[CURL_ERROR_SIZE];
+	struct curl_slist *headers = NULL;
+	struct upload_buffer upload_data;
+	json_t *val, *err_val, *res_val;
 	bool probing = false;
 	bool probing = false;
+	json_error_t err;
+	int rc;
 
 
 	memset(&err, 0, sizeof(err));
 	memset(&err, 0, sizeof(err));
 
 
@@ -325,6 +325,13 @@ json_t *json_rpc_call(CURL *curl, const char *url,
 		"Content-type: application/json");
 		"Content-type: application/json");
 	headers = curl_slist_append(headers,
 	headers = curl_slist_append(headers,
 		"X-Mining-Extensions: longpoll midstate rollntime submitold");
 		"X-Mining-Extensions: longpoll midstate rollntime submitold");
+
+	if (likely(global_hashrate)) {
+		asprintf(&ghashrate, "X-Mining-Hashrate: %llu", global_hashrate);
+		headers = curl_slist_append(headers, ghashrate);
+		free(ghashrate);
+	}
+
 	headers = curl_slist_append(headers, len_hdr);
 	headers = curl_slist_append(headers, len_hdr);
 	headers = curl_slist_append(headers, user_agent_hdr);
 	headers = curl_slist_append(headers, user_agent_hdr);
 	headers = curl_slist_append(headers, "Expect:"); /* disable Expect hdr*/
 	headers = curl_slist_append(headers, "Expect:"); /* disable Expect hdr*/