API allow multiple commands/replies in one request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
diff --git a/API-README b/API-README
index 5d27855..b50341b 100644
--- a/API-README
+++ b/API-README
@@ -109,6 +109,36 @@ The STATUS section is:
This defaults to the cgminer version but is the value of --api-description
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:
The list of requests - a (*) means it requires privileged access - and replies:
@@ -453,7 +483,13 @@ miner.php - an example web page to access 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
diff --git a/api.c b/api.c
index c71dad9..d63bbe5 100644
--- a/api.c
+++ b/api.c
@@ -34,13 +34,6 @@
#define HAVE_AN_FPGA 1
#endif
-// Big enough for largest API request
-// though a PC with 100s of PGAs may exceed the size ...
-// 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
#define TMPBUFSIZ 8192
@@ -131,8 +124,11 @@ static const char *COMMA = ",";
#define COMSTR ","
static const char SEPARATOR = '|';
#define SEPSTR "|"
+#define CMDJOIN '+'
+#define JOIN_CMD "CMD="
+#define BETWEEN_JOIN SEPSTR
-static const char *APIVERSION = "3.0";
+static const char *APIVERSION = "3.1";
static const char *DEAD = "Dead";
static const char *SICK = "Sick";
static const char *NOSTART = "NoStart";
@@ -277,6 +273,7 @@ static const char ISJSON = '{';
#define JSON_USBSTATS JSON1 _USBSTATS JSON2
#define JSON_END JSON4 JSON5
#define JSON_END_TRUNCATED JSON4_TRUNCATED JSON5
+#define JSON_BETWEEN_JOIN ","
static const char *JSON_COMMAND = "command";
static const char *JSON_PARAMETER = "parameter";
@@ -1348,10 +1345,8 @@ static void message(struct io_data *io_data, int messageid, int paramid, char *p
#endif
int i;
- io_reinit(io_data);
-
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++) {
if (codes[i].code == messageid) {
@@ -3777,54 +3772,55 @@ struct CMDS {
char *name;
void (*func)(struct io_data *, SOCKETTYPE, char *, bool, char);
bool iswritemode;
+ bool joinable;
} cmds[] = {
- { "version", apiversion, false },
- { "config", minerconfig, false },
- { "devs", devstatus, false },
- { "pools", poolstatus, false },
- { "summary", summary, false },
+ { "version", apiversion, false, true },
+ { "config", minerconfig, false, true },
+ { "devs", devstatus, false, true },
+ { "pools", poolstatus, false, true },
+ { "summary", summary, false, true },
#ifdef HAVE_AN_FPGA
- { "pga", pgadev, false },
- { "pgaenable", pgaenable, true },
- { "pgadisable", pgadisable, true },
- { "pgaidentify", pgaidentify, true },
+ { "pga", pgadev, false, false },
+ { "pgaenable", pgaenable, true, false },
+ { "pgadisable", pgadisable, true, false },
+ { "pgaidentify", pgaidentify, true, false },
#endif
- { "pgacount", pgacount, false },
- { "switchpool", switchpool, true },
- { "addpool", addpool, true },
- { "poolpriority", poolpriority, true },
- { "poolquota", poolquota, true },
- { "enablepool", enablepool, true },
- { "disablepool", disablepool, true },
- { "removepool", removepool, true },
- { "save", dosave, true },
- { "quit", doquit, true },
- { "privileged", privileged, true },
- { "notify", notify, false },
- { "devdetails", devdetails, false },
- { "restart", dorestart, true },
- { "stats", minerstats, false },
- { "check", checkcommand, false },
- { "failover-only", failoveronly, true },
- { "coin", minecoin, false },
- { "debug", debugstate, true },
- { "setconfig", setconfig, true },
- { "usbstats", usbstats, false },
+ { "pgacount", pgacount, false, true },
+ { "switchpool", switchpool, true, false },
+ { "addpool", addpool, true, false },
+ { "poolpriority", poolpriority, true, false },
+ { "poolquota", poolquota, true, false },
+ { "enablepool", enablepool, true, false },
+ { "disablepool", disablepool, true, false },
+ { "removepool", removepool, true, false },
+ { "save", dosave, true, false },
+ { "quit", doquit, true, false },
+ { "privileged", privileged, true, false },
+ { "notify", notify, false, true },
+ { "devdetails", devdetails, false, true },
+ { "restart", dorestart, true, false },
+ { "stats", minerstats, false, true },
+ { "check", checkcommand, false, false },
+ { "failover-only", failoveronly, true, false },
+ { "coin", minecoin, false, true },
+ { "debug", debugstate, true, false },
+ { "setconfig", setconfig, true, false },
+ { "usbstats", usbstats, false, true },
#ifdef HAVE_AN_FPGA
- { "pgaset", pgaset, true },
+ { "pgaset", pgaset, true, false },
#endif
- { "zero", dozero, true },
- { "hotplug", dohotplug, true },
+ { "zero", dozero, true, false },
+ { "hotplug", dohotplug, true, false },
#ifdef HAVE_AN_ASIC
- { "asc", ascdev, false },
- { "ascenable", ascenable, true },
- { "ascdisable", ascdisable, true },
- { "ascidentify", ascidentify, true },
- { "ascset", ascset, true },
+ { "asc", ascdev, false, false },
+ { "ascenable", ascenable, true, false },
+ { "ascdisable", ascdisable, true, false },
+ { "ascidentify", ascidentify, true, false },
+ { "ascset", ascset, true, false },
#endif
- { "asccount", asccount, false },
- { "lockstats", lockstats, true },
- { NULL, NULL, false }
+ { "asccount", asccount, false, true },
+ { "lockstats", lockstats, true, true },
+ { NULL, NULL, false, false }
};
static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool isjson, char group)
@@ -3865,6 +3861,49 @@ static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, c
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)
{
int count, sendc, res, tosend, len, n;
@@ -4429,7 +4468,7 @@ void api(int api_thr_id)
struct sockaddr_in cli;
socklen_t clisiz;
char cmdbuf[100];
- char *cmd = NULL;
+ char *cmd = NULL, *cmdptr, *cmdsbuf;
char *param;
bool addrok;
char group;
@@ -4437,7 +4476,7 @@ void api(int api_thr_id)
json_t *json_config = NULL;
json_t *json_val;
bool isjson;
- bool did;
+ bool did, isjoin, firstjoin;
int i;
SOCKETTYPE *apisock;
@@ -4640,27 +4679,80 @@ void api(int api_thr_id)
}
if (!did) {
- for (i = 0; cmds[i].name != NULL; i++) {
- if (strcmp(cmd, cmds[i].name) == 0) {
- sprintf(cmdbuf, "|%s|", cmd);
- 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);
+ if (strchr(cmd, CMDJOIN)) {
+ firstjoin = isjoin = true;
+ // cmd + leading '|' + '\0'
+ cmdsbuf = malloc(strlen(cmd) + 2);
+ if (!cmdsbuf)
+ quithere(1, "OOM cmdsbuf");
+ strcpy(cmdsbuf, "|");
+ 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);
- did = true;
- break;
+ if (!did) {
+ if (isjoin)
+ 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) {
- message(io_data, MSG_INVCMD, 0, NULL, isjson);
+ if (isjoin)
send_result(io_data, c, isjson);
- }
+
if (isjson && json_is_object(json_config))
json_decref(json_config);
}