Browse Source

API allow multiple commands/replies in one request

port-ckolivas
Kano 11 years ago committed by Noel Maersk
parent
commit
ae837a762f
  1. 227
      api.c
  2. 38
      doc/API

227
api.c

@ -29,12 +29,6 @@
#include "miner.h" #include "miner.h"
#include "util.h" #include "util.h"
// Big enough for largest API request
// data is truncated at the end of the last record that fits
// but still closed correctly for JSON
// Current code assumes it can socket send this size + JSON_CLOSE + JSON_END
#define SOCKBUFSIZ 65432
// BUFSIZ varies on Windows and Linux // BUFSIZ varies on Windows and Linux
#define TMPBUFSIZ 8192 #define TMPBUFSIZ 8192
@ -126,7 +120,11 @@ static const char SEPARATOR = '|';
#define SEPSTR "|" #define SEPSTR "|"
static const char GPUSEP = ','; static const char GPUSEP = ',';
static const char *APIVERSION = "3.0"; #define CMDJOIN '+'
#define JOIN_CMD "CMD="
#define BETWEEN_JOIN SEPSTR
static const char *APIVERSION = "3.1";
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";
@ -215,9 +213,10 @@ static const char ISJSON = '{';
#define JSON_MINECOIN JSON1 _MINECOIN JSON2 #define JSON_MINECOIN JSON1 _MINECOIN JSON2
#define JSON_DEBUGSET JSON1 _DEBUGSET JSON2 #define JSON_DEBUGSET JSON1 _DEBUGSET JSON2
#define JSON_SETCONFIG JSON1 _SETCONFIG JSON2 #define JSON_SETCONFIG JSON1 _SETCONFIG JSON2
#define JSON_USBSTATS JSON1 _USBSTATS JSON2
#define JSON_END JSON4 JSON5 #define JSON_END JSON4 JSON5
#define JSON_END_TRUNCATED JSON4_TRUNCATED JSON5 #define JSON_END_TRUNCATED JSON4_TRUNCATED JSON5
#define JSON_BETWEEN_JOIN ","
static const char *JSON_COMMAND = "command"; static const char *JSON_COMMAND = "command";
static const char *JSON_PARAMETER = "parameter"; static const char *JSON_PARAMETER = "parameter";
@ -296,7 +295,7 @@ static const char *JSON_PARAMETER = "parameter";
#define MSG_INVNUM 84 #define MSG_INVNUM 84
#define MSG_CONPAR 85 #define MSG_CONPAR 85
#define MSG_CONVAL 86 #define MSG_CONVAL 86
#define MSG_USBSTA 87
#define MSG_NOUSTA 88 #define MSG_NOUSTA 88
#define MSG_ZERMIS 94 #define MSG_ZERMIS 94
@ -418,7 +417,6 @@ struct CODES {
{ SEVERITY_SUCC, MSG_SETQUOTA,PARAM_SET, "Set pool '%s' to quota %d'" }, { SEVERITY_SUCC, MSG_SETQUOTA,PARAM_SET, "Set pool '%s' to quota %d'" },
{ SEVERITY_ERR, MSG_CONPAR, PARAM_NONE, "Missing config parameters 'name,N'" }, { SEVERITY_ERR, MSG_CONPAR, PARAM_NONE, "Missing config parameters 'name,N'" },
{ SEVERITY_ERR, MSG_CONVAL, PARAM_STR, "Missing config value N for '%s,N'" }, { SEVERITY_ERR, MSG_CONVAL, PARAM_STR, "Missing config value N for '%s,N'" },
{ SEVERITY_SUCC, MSG_USBSTA, PARAM_NONE, "USB Statistics" },
{ SEVERITY_INFO, MSG_NOUSTA, PARAM_NONE, "No USB Statistics" }, { SEVERITY_INFO, MSG_NOUSTA, PARAM_NONE, "No USB Statistics" },
{ SEVERITY_ERR, MSG_ZERMIS, PARAM_NONE, "Missing zero parameters" }, { SEVERITY_ERR, MSG_ZERMIS, PARAM_NONE, "Missing zero parameters" },
{ SEVERITY_ERR, MSG_ZERINV, PARAM_STR, "Invalid zero parameter '%s'" }, { SEVERITY_ERR, MSG_ZERINV, PARAM_STR, "Invalid zero parameter '%s'" },
@ -1061,10 +1059,8 @@ static void message(struct io_data *io_data, int messageid, int paramid, char *p
int i; int i;
io_reinit(io_data);
if (isjson) if (isjson)
io_put(io_data, JSON_START JSON_STATUS); io_add(io_data, JSON_START JSON_STATUS);
for (i = 0; codes[i].severity != SEVERITY_FAIL; i++) { for (i = 0; codes[i].severity != SEVERITY_FAIL; i++) {
if (codes[i].code == messageid) { if (codes[i].code == messageid) {
@ -3045,44 +3041,45 @@ struct CMDS {
char *name; char *name;
void (*func)(struct io_data *, SOCKETTYPE, char *, bool, char); void (*func)(struct io_data *, SOCKETTYPE, char *, bool, char);
bool iswritemode; bool iswritemode;
bool joinable;
} cmds[] = { } cmds[] = {
{ "version", apiversion, false }, { "version", apiversion, false, true },
{ "config", minerconfig, false }, { "config", minerconfig, false, true },
{ "devs", devstatus, false }, { "devs", devstatus, false, true },
{ "pools", poolstatus, false }, { "pools", poolstatus, false, true },
{ "summary", summary, false }, { "summary", summary, false, true },
{ "gpuenable", gpuenable, true }, { "gpuenable", gpuenable, true, false },
{ "gpudisable", gpudisable, true }, { "gpudisable", gpudisable, true, false },
{ "gpurestart", gpurestart, true }, { "gpurestart", gpurestart, true, false },
{ "gpu", gpudev, false }, { "gpu", gpudev, false, false },
{ "gpucount", gpucount, false }, { "gpucount", gpucount, false, true },
{ "switchpool", switchpool, true }, { "switchpool", switchpool, true, false },
{ "addpool", addpool, true }, { "addpool", addpool, true, false },
{ "poolpriority", poolpriority, true }, { "poolpriority", poolpriority, true, false },
{ "poolquota", poolquota, true }, { "poolquota", poolquota, true, false },
{ "enablepool", enablepool, true }, { "enablepool", enablepool, true, false },
{ "disablepool", disablepool, true }, { "disablepool", disablepool, true, false },
{ "removepool", removepool, true }, { "removepool", removepool, true, false },
{ "gpuintensity", gpuintensity, true }, { "gpuintensity", gpuintensity, true, false },
{ "gpumem", gpumem, true }, { "gpumem", gpumem, true, false },
{ "gpuengine", gpuengine, true }, { "gpuengine", gpuengine, true, false },
{ "gpufan", gpufan, true }, { "gpufan", gpufan, true, false },
{ "gpuvddc", gpuvddc, true }, { "gpuvddc", gpuvddc, true, false },
{ "save", dosave, true }, { "save", dosave, true, false },
{ "quit", doquit, true }, { "quit", doquit, true, false },
{ "privileged", privileged, true }, { "privileged", privileged, true, false },
{ "notify", notify, false }, { "notify", notify, false, true },
{ "devdetails", devdetails, false }, { "devdetails", devdetails, false, true },
{ "restart", dorestart, true }, { "restart", dorestart, true, false },
{ "stats", minerstats, false }, { "stats", minerstats, false, true },
{ "check", checkcommand, false }, { "check", checkcommand, false, false },
{ "failover-only", failoveronly, true }, { "failover-only", failoveronly, true, false },
{ "coin", minecoin, false }, { "coin", minecoin, false, true },
{ "debug", debugstate, true }, { "debug", debugstate, true, false },
{ "setconfig", setconfig, true }, { "setconfig", setconfig, true, false },
{ "zero", dozero, true }, { "zero", dozero, true, false },
{ "lockstats", lockstats, true }, { "lockstats", lockstats, true, true },
{ NULL, NULL, false } { NULL, NULL, false, false }
}; };
static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group) static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group)
@ -3125,6 +3122,49 @@ static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, c
io_close(io_data); io_close(io_data);
} }
static void head_join(struct io_data *io_data, char *cmdptr, bool isjson, bool *firstjoin)
{
char *ptr;
if (*firstjoin) {
if (isjson)
io_add(io_data, JSON0);
*firstjoin = false;
} else {
if (isjson)
io_add(io_data, JSON_BETWEEN_JOIN);
}
// External supplied string
ptr = escape_string(cmdptr, isjson);
if (isjson) {
io_add(io_data, JSON1);
io_add(io_data, ptr);
io_add(io_data, JSON2);
} else {
io_add(io_data, JOIN_CMD);
io_add(io_data, ptr);
io_add(io_data, BETWEEN_JOIN);
}
if (ptr != cmdptr)
free(ptr);
}
static void tail_join(struct io_data *io_data, bool isjson)
{
if (io_data->close) {
io_add(io_data, JSON_CLOSE);
io_data->close = false;
}
if (isjson) {
io_add(io_data, JSON_END);
io_add(io_data, JSON3);
}
}
static void send_result(struct io_data *io_data, SOCKETTYPE c, bool isjson) static void send_result(struct io_data *io_data, SOCKETTYPE c, bool isjson)
{ {
int count, sendc, res, tosend, len, n; int count, sendc, res, tosend, len, n;
@ -3689,7 +3729,7 @@ void api(int api_thr_id)
struct sockaddr_in cli; struct sockaddr_in cli;
socklen_t clisiz; socklen_t clisiz;
char cmdbuf[100]; char cmdbuf[100];
char *cmd = NULL; char *cmd = NULL, *cmdptr, *cmdsbuf;
char *param; char *param;
bool addrok; bool addrok;
char group; char group;
@ -3697,7 +3737,7 @@ void api(int api_thr_id)
json_t *json_config = NULL; json_t *json_config = NULL;
json_t *json_val; json_t *json_val;
bool isjson; bool isjson;
bool did; bool did, isjoin, firstjoin;
int i; int i;
SOCKETTYPE *apisock; SOCKETTYPE *apisock;
@ -3898,27 +3938,80 @@ void api(int api_thr_id)
} }
if (!did) { if (!did) {
for (i = 0; cmds[i].name != NULL; i++) { if (strchr(cmd, CMDJOIN)) {
if (strcmp(cmd, cmds[i].name) == 0) { firstjoin = isjoin = true;
sprintf(cmdbuf, "|%s|", cmd); // cmd + leading '|' + '\0'
if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf)) cmdsbuf = malloc(strlen(cmd) + 2);
(cmds[i].func)(io_data, c, param, isjson, group); if (!cmdsbuf)
else { quithere(1, "OOM cmdsbuf");
message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson); strcpy(cmdsbuf, "|");
applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name); param = NULL;
} else
firstjoin = isjoin = false;
cmdptr = cmd;
do {
did = false;
if (isjoin) {
cmd = strchr(cmdptr, CMDJOIN);
if (cmd)
*(cmd++) = '\0';
if (!*cmdptr)
goto inochi;
}
for (i = 0; cmds[i].name != NULL; i++) {
if (strcmp(cmdptr, cmds[i].name) == 0) {
sprintf(cmdbuf, "|%s|", cmdptr);
if (isjoin) {
if (strstr(cmdsbuf, cmdbuf)) {
did = true;
break;
}
strcat(cmdsbuf, cmdptr);
strcat(cmdsbuf, "|");
head_join(io_data, cmdptr, isjson, &firstjoin);
if (!cmds[i].joinable) {
message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson);
did = true;
tail_join(io_data, isjson);
break;
}
}
if (ISPRIVGROUP(group) || strstr(COMMANDS(group), cmdbuf))
(cmds[i].func)(io_data, c, param, isjson, group);
else {
message(io_data, MSG_ACCDENY, 0, cmds[i].name, isjson);
applog(LOG_DEBUG, "API: access denied to '%s' for '%s' command", connectaddr, cmds[i].name);
}
did = true;
if (!isjoin)
send_result(io_data, c, isjson);
else
tail_join(io_data, isjson);
break;
} }
}
send_result(io_data, c, isjson); if (!did) {
did = true; if (isjoin)
break; head_join(io_data, cmdptr, isjson, &firstjoin);
message(io_data, MSG_INVCMD, 0, NULL, isjson);
if (isjoin)
tail_join(io_data, isjson);
else
send_result(io_data, c, isjson);
} }
} inochi:
if (isjoin)
cmdptr = cmd;
} while (isjoin && cmdptr);
} }
if (!did) { if (isjoin)
message(io_data, MSG_INVCMD, 0, NULL, isjson);
send_result(io_data, c, isjson); send_result(io_data, c, isjson);
}
if (isjson && json_is_object(json_config)) if (isjson && json_is_object(json_config))
json_decref(json_config); json_decref(json_config);
} }

38
doc/API

@ -109,6 +109,36 @@ The STATUS section is:
This defaults to the sgminer version but is the value of --api-description This defaults to the sgminer version but is the value of --api-description
if it was specified at runtime. if it was specified at runtime.
With API V3.1 you can also request multiple report replies in a single command
request
e.g. to request both summary and devs, the command would be summary+devs
This is only available for report commands that don't need parameters,
and is not available for commands that change anything
Any parameters supplied will be ignored
The extra formatting of the result is to have a section for each command
e.g. summary|STATUS=....|devs|STATUS=...
With JSON, each result is within a section of the command name
e.g. {"summary":{"STATUS":[{"STATUS":"S"...}],"SUMMARY":[...],"id":1},
"devs":{"STATUS":[{"STATUS:"S"...}],"DEVS":[...],"id":1},"id":1}
As before, if you supply bad JSON you'll just get a single 'E' STATUS section
in the old format, since it doesn't switch to using the new format until it
correctly processes the JSON and can match a '+' in the command
If you request a command multiple times, e.g. devs+devs
you'll just get it once
If this results in only one command, it will still use the new layout
with just the one command
If you request a command that can't be used due to requiring parameters,
a command that isn't a report, or an invalid command, you'll get an 'E' STATUS
for that one but it will still attempt to process all other commands supplied
Blank/missing commands are ignore e.g. +devs++
will just show 'devs' using the new layout
For API version 1.10 and later: For API version 1.10 and later:
The list of requests - a (*) means it requires privileged access - and replies: The list of requests - a (*) means it requires privileged access - and replies:
@ -497,7 +527,13 @@ 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 V3.0 (cgminer v3.9.1) API V3.1 (cgminer v3.12.1)
Multiple report request command with '+' e.g. summary+devs
---------
API V3.0 (cgminer v3.11.0)
Allow unlimited size replies Allow unlimited size replies

Loading…
Cancel
Save