0% found this document useful (0 votes)
76 views

API

Uploaded by

alexandr
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
76 views

API

Uploaded by

alexandr
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 94

/*

* Copyright 2011-2015 Andrew Smith


* Copyright 2011-2015,2018 Con Kolivas
*
* 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.
*/
#define _MEMORY_DEBUG_MASTER 1

#include "config.h"

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <limits.h>
#include <sys/types.h>

#include "compat.h"
#include "miner.h"
#include "util.h"
#include "klist.h"

#if defined(USE_BFLSC) || defined(USE_AVALON) || defined(USE_AVALON2) ||


defined(USE_AVALON4) || \
defined(USE_HASHFAST) || defined(USE_BITFURY) || defined(USE_BITFURY16) ||
defined(USE_BLOCKERUPTER) || defined(USE_KLONDIKE) || \
defined(USE_KNC) || defined(USE_BAB) || defined(USE_DRAGONMINT_T1) ||
defined(USE_DRILLBIT) || \
defined(USE_MINION) || defined(USE_COINTERRA) || defined(USE_BITMINE_A1) || \
defined(USE_ANT_S1) || defined(USE_ANT_S2) || defined(USE_ANT_S3) ||
defined(USE_SP10) || \
defined(USE_SP30) || defined(USE_ICARUS) || defined(USE_HASHRATIO) ||
defined(USE_AVALON_MINER) || \
defined(USE_AVALON7)
#define HAVE_AN_ASIC 1
#endif

#if defined(USE_BITFORCE) || defined(USE_MODMINER)


#define HAVE_AN_FPGA 1
#endif

// BUFSIZ varies on Windows and Linux


#define TMPBUFSIZ 8192

// Number of requests to queue - normally would be small


// However lots of PGA's may mean more
#define QUEUE 100

#if defined WIN32


static char WSAbuf[1024];

struct WSAERRORS {
int id;
char *code;
} WSAErrors[] = {
{ 0, "No error" },
{ WSAEINTR, "Interrupted system call" },
{ WSAEBADF, "Bad file number" },
{ WSAEACCES, "Permission denied" },
{ WSAEFAULT, "Bad address" },
{ WSAEINVAL, "Invalid argument" },
{ WSAEMFILE, "Too many open sockets" },
{ WSAEWOULDBLOCK, "Operation would block" },
{ WSAEINPROGRESS, "Operation now in progress" },
{ WSAEALREADY, "Operation already in progress" },
{ WSAENOTSOCK, "Socket operation on non-socket" },
{ WSAEDESTADDRREQ, "Destination address required" },
{ WSAEMSGSIZE, "Message too long" },
{ WSAEPROTOTYPE, "Protocol wrong type for socket" },
{ WSAENOPROTOOPT, "Bad protocol option" },
{ WSAEPROTONOSUPPORT, "Protocol not supported" },
{ WSAESOCKTNOSUPPORT, "Socket type not supported" },
{ WSAEOPNOTSUPP, "Operation not supported on socket" },
{ WSAEPFNOSUPPORT, "Protocol family not supported" },
{ WSAEAFNOSUPPORT, "Address family not supported" },
{ WSAEADDRINUSE, "Address already in use" },
{ WSAEADDRNOTAVAIL, "Can't assign requested address" },
{ WSAENETDOWN, "Network is down" },
{ WSAENETUNREACH, "Network is unreachable" },
{ WSAENETRESET, "Net connection reset" },
{ WSAECONNABORTED, "Software caused connection abort" },
{ WSAECONNRESET, "Connection reset by peer" },
{ WSAENOBUFS, "No buffer space available" },
{ WSAEISCONN, "Socket is already connected" },
{ WSAENOTCONN, "Socket is not connected" },
{ WSAESHUTDOWN, "Can't send after socket shutdown" },
{ WSAETOOMANYREFS, "Too many references, can't splice" },
{ WSAETIMEDOUT, "Connection timed out" },
{ WSAECONNREFUSED, "Connection refused" },
{ WSAELOOP, "Too many levels of symbolic links" },
{ WSAENAMETOOLONG, "File name too long" },
{ WSAEHOSTDOWN, "Host is down" },
{ WSAEHOSTUNREACH, "No route to host" },
{ WSAENOTEMPTY, "Directory not empty" },
{ WSAEPROCLIM, "Too many processes" },
{ WSAEUSERS, "Too many users" },
{ WSAEDQUOT, "Disc quota exceeded" },
{ WSAESTALE, "Stale NFS file handle" },
{ WSAEREMOTE, "Too many levels of remote in path" },
{ WSASYSNOTREADY, "Network system is unavailable" },
{ WSAVERNOTSUPPORTED, "Winsock version out of range" },
{ WSANOTINITIALISED, "WSAStartup not yet called" },
{ WSAEDISCON, "Graceful shutdown in progress" },
{ WSAHOST_NOT_FOUND, "Host not found" },
{ WSANO_DATA, "No host data of that type was found" },
{ -1, "Unknown error code" }
};

char *WSAErrorMsg(void) {
int i;
int id = WSAGetLastError();
/* Assume none of them are actually -1 */
for (i = 0; WSAErrors[i].id != -1; i++)
if (WSAErrors[i].id == id)
break;

sprintf(WSAbuf, "Socket Error: (%d) %s", id, WSAErrors[i].code);

return &(WSAbuf[0]);
}
#endif

#if defined(__APPLE__) || defined(__FreeBSD__)


#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
#endif

static const char *UNAVAILABLE = " - API will not be available";


static const char *MUNAVAILABLE = " - API multicast listener will not be
available";

static const char *BLANK = "";


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.7";


static const char *DEAD = "Dead";
static const char *SICK = "Sick";
static const char *NOSTART = "NoStart";
static const char *INIT = "Initialising";
static const char *DISABLED = "Disabled";
static const char *ALIVE = "Alive";
static const char *REJECTING = "Rejecting";
static const char *UNKNOWN = "Unknown";

static __maybe_unused const char *NONE = "None";

static const char *YES = "Y";


static const char *NO = "N";
static const char *NULLSTR = "(null)";

static const char *TRUESTR = "true";


static const char *FALSESTR = "false";

static const char *SHA256STR = "sha256";

static const char *DEVICECODE = ""


#ifdef USE_ANT_S1
"ANT "
#endif
#ifdef USE_ANT_S2
"AS2 "
#endif
#ifdef USE_ANT_S3
"AS3 "
#endif
#ifdef USE_AVALON
"AVA "
#endif
#ifdef USE_BAB
"BaB "
#endif
#ifdef USE_BFLSC
"BAS "
#endif
#ifdef USE_BITFORCE
"BFL "
#endif
#ifdef USE_BITFURY
"BFU "
#endif
#ifdef USE_BLOCKERUPTER
"BET "
#endif
#ifdef USE_DRILLBIT
"DRB "
#endif
#ifdef USE_DRAGONMINT_T1
"DT1 "
#endif
#ifdef USE_HASHFAST
"HFA "
#endif
#ifdef USE_HASHRATIO
"HRO "
#endif
#ifdef USE_BITMINE_A1
"BA1 "
#endif
#ifdef USE_ICARUS
"ICA "
#endif
#ifdef USE_KNC
"KnC "
#endif
#ifdef USE_MINION
"MBA "
#endif
#ifdef USE_MODMINER
"MMQ "
#endif
#ifdef USE_COINTERRA
"CTA "
#endif
#ifdef USE_SP10
"SPN "
#endif
#ifdef USE_SP30
"S30 "
#endif

"";
static const char *OSINFO =
#if defined(__linux)
"Linux";
#else
#if defined(__APPLE__)
"Apple";
#else
#if defined (WIN32)
"Windows";
#else
#if defined(unix)
"Unix";
#else
"Unknown";
#endif
#endif
#endif
#endif

#define _DEVS "DEVS"


#define _POOLS "POOLS"
#define _SUMMARY "SUMMARY"
#define _STATUS "STATUS"
#define _VERSION "VERSION"
#define _MINECONFIG "CONFIG"

#ifdef HAVE_AN_FPGA
#define _PGA "PGA"
#endif

#ifdef HAVE_AN_ASIC
#define _ASC "ASC"
#endif

#define _PGAS "PGAS"


#define _ASCS "ASCS"
#define _NOTIFY "NOTIFY"
#define _DEVDETAILS "DEVDETAILS"
#define _BYE "BYE"
#define _RESTART "RESTART"
#define _MINESTATS "STATS"
#define _MINEDEBUG "DBGSTATS"
#define _CHECK "CHECK"
#define _MINECOIN "COIN"
#define _DEBUGSET "DEBUG"
#define _SETCONFIG "SETCONFIG"
#define _USBSTATS "USBSTATS"
#define _LCD "LCD"

static const char ISJSON = '{';


#define JSON0 "{"
#define JSON1 "\""
#define JSON2 "\":["
#define JSON3 "]"
#define JSON4 ",\"id\":1"
// If anyone cares, id=0 for truncated output
#define JSON4_TRUNCATED ",\"id\":0"
#define JSON5 "}"
#define JSON6 "\":"
#define JSON_START JSON0
#define JSON_DEVS JSON1 _DEVS JSON2
#define JSON_POOLS JSON1 _POOLS JSON2
#define JSON_SUMMARY JSON1 _SUMMARY JSON2
#define JSON_STATUS JSON1 _STATUS JSON2
#define JSON_VERSION JSON1 _VERSION JSON2
#define JSON_MINECONFIG JSON1 _MINECONFIG JSON2
#define JSON_ACTION JSON0 JSON1 _STATUS JSON6

#ifdef HAVE_AN_FPGA
#define JSON_PGA JSON1 _PGA JSON2
#endif

#ifdef HAVE_AN_ASIC
#define JSON_ASC JSON1 _ASC JSON2
#endif

#define JSON_PGAS JSON1 _PGAS JSON2


#define JSON_ASCS JSON1 _ASCS JSON2
#define JSON_NOTIFY JSON1 _NOTIFY JSON2
#define JSON_DEVDETAILS JSON1 _DEVDETAILS JSON2
#define JSON_BYE JSON1 _BYE JSON1
#define JSON_RESTART JSON1 _RESTART JSON1
#define JSON_CLOSE JSON3
#define JSON_MINESTATS JSON1 _MINESTATS JSON2
#define JSON_MINEDEBUG JSON1 _MINEDEBUG JSON2
#define JSON_CHECK JSON1 _CHECK JSON2
#define JSON_MINECOIN JSON1 _MINECOIN JSON2
#define JSON_DEBUGSET JSON1 _DEBUGSET JSON2
#define JSON_SETCONFIG JSON1 _SETCONFIG JSON2
#define JSON_USBSTATS JSON1 _USBSTATS JSON2
#define JSON_LCD JSON1 _LCD 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";

#define MSG_POOL 7
#define MSG_NOPOOL 8
#define MSG_DEVS 9
#define MSG_NODEVS 10
#define MSG_SUMM 11
#define MSG_INVCMD 14
#define MSG_MISID 15

#define MSG_VERSION 22
#define MSG_INVJSON 23
#define MSG_MISCMD 24
#define MSG_MISPID 25
#define MSG_INVPID 26
#define MSG_SWITCHP 27
#define MSG_MISVAL 28
#define MSG_NOADL 29
#define MSG_INVINT 31
#define MSG_MINECONFIG 33
#define MSG_MISFN 42
#define MSG_BADFN 43
#define MSG_SAVED 44
#define MSG_ACCDENY 45
#define MSG_ACCOK 46
#define MSG_ENAPOOL 47
#define MSG_DISPOOL 48
#define MSG_ALRENAP 49
#define MSG_ALRDISP 50
#define MSG_DISLASTP 51
#define MSG_MISPDP 52
#define MSG_INVPDP 53
#define MSG_TOOMANYP 54
#define MSG_ADDPOOL 55

#ifdef HAVE_AN_FPGA
#define MSG_PGANON 56
#define MSG_PGADEV 57
#define MSG_INVPGA 58
#endif

#define MSG_NUMPGA 59
#define MSG_NOTIFY 60

#ifdef HAVE_AN_FPGA
#define MSG_PGALRENA 61
#define MSG_PGALRDIS 62
#define MSG_PGAENA 63
#define MSG_PGADIS 64
#define MSG_PGAUNW 65
#endif

#define MSG_REMLASTP 66
#define MSG_ACTPOOL 67
#define MSG_REMPOOL 68
#define MSG_DEVDETAILS 69
#define MSG_MINESTATS 70
#define MSG_MISCHK 71
#define MSG_CHECK 72
#define MSG_POOLPRIO 73
#define MSG_DUPPID 74
#define MSG_MISBOOL 75
#define MSG_INVBOOL 76
#define MSG_FOO 77
#define MSG_MINECOIN 78
#define MSG_DEBUGSET 79
#define MSG_PGAIDENT 80
#define MSG_PGANOID 81
#define MSG_SETCONFIG 82
#define MSG_UNKCON 83
#define MSG_INVNUM 84
#define MSG_CONPAR 85
#define MSG_CONVAL 86
#define MSG_USBSTA 87
#define MSG_NOUSTA 88

#ifdef HAVE_AN_FPGA
#define MSG_MISPGAOPT 89
#define MSG_PGANOSET 90
#define MSG_PGAHELP 91
#define MSG_PGASETOK 92
#define MSG_PGASETERR 93
#endif

#define MSG_ZERMIS 94
#define MSG_ZERINV 95
#define MSG_ZERSUM 96
#define MSG_ZERNOSUM 97
#define MSG_PGAUSBNODEV 98
#define MSG_INVHPLG 99
#define MSG_HOTPLUG 100
#define MSG_DISHPLG 101
#define MSG_NOHPLG 102
#define MSG_MISHPLG 103

#define MSG_NUMASC 104


#ifdef HAVE_AN_ASIC
#define MSG_ASCNON 105
#define MSG_ASCDEV 106
#define MSG_INVASC 107
#define MSG_ASCLRENA 108
#define MSG_ASCLRDIS 109
#define MSG_ASCENA 110
#define MSG_ASCDIS 111
#define MSG_ASCUNW 112
#define MSG_ASCIDENT 113
#define MSG_ASCNOID 114
#endif
#define MSG_ASCUSBNODEV 115

#ifdef HAVE_AN_ASIC
#define MSG_MISASCOPT 116
#define MSG_ASCNOSET 117
#define MSG_ASCHELP 118
#define MSG_ASCSETOK 119
#define MSG_ASCSETERR 120
#endif

#define MSG_INVNEG 121


#define MSG_SETQUOTA 122
#define MSG_LOCKOK 123
#define MSG_LOCKDIS 124
#define MSG_LCD 125

#define MSG_MINEDEBUG 126

#define MSG_DEPRECATED 127

enum code_severity {
SEVERITY_ERR,
SEVERITY_WARN,
SEVERITY_INFO,
SEVERITY_SUCC,
SEVERITY_FAIL
};

enum code_parameters {
PARAM_PGA,
PARAM_ASC,
PARAM_PID,
PARAM_PGAMAX,
PARAM_ASCMAX,
PARAM_PMAX,
PARAM_POOLMAX,

// Single generic case: have the code resolve it - see below


PARAM_DMAX,

PARAM_CMD,
PARAM_POOL,
PARAM_STR,
PARAM_BOTH,
PARAM_BOOL,
PARAM_SET,
PARAM_INT,
PARAM_NONE
};

struct CODES {
const enum code_severity severity;
const int code;
const enum code_parameters params;
const char *description;
} codes[] = {
{ SEVERITY_SUCC, MSG_POOL, PARAM_PMAX, "%d Pool(s)" },
{ SEVERITY_ERR, MSG_NOPOOL, PARAM_NONE, "No pools" },

{ SEVERITY_SUCC, MSG_DEVS, PARAM_DMAX,


#ifdef HAVE_AN_ASIC
"%d ASC(s)"
#endif
#if defined(HAVE_AN_ASIC) && defined(HAVE_AN_FPGA)
" - "
#endif
#ifdef HAVE_AN_FPGA
"%d PGA(s)"
#endif
},

{ SEVERITY_ERR, MSG_NODEVS, PARAM_NONE, "No "


#ifdef HAVE_AN_ASIC
"ASCs"
#endif
#if defined(HAVE_AN_ASIC) && defined(HAVE_AN_FPGA)
"/"
#endif
#ifdef HAVE_AN_FPGA
"PGAs"
#endif
},

{ SEVERITY_SUCC, MSG_SUMM, PARAM_NONE, "Summary" },


{ SEVERITY_ERR, MSG_INVCMD, PARAM_NONE, "Invalid command" },
{ SEVERITY_ERR, MSG_MISID, PARAM_NONE, "Missing device id parameter" },
#ifdef HAVE_AN_FPGA
{ SEVERITY_ERR, MSG_PGANON, PARAM_NONE, "No PGAs" },
{ SEVERITY_SUCC, MSG_PGADEV, PARAM_PGA, "PGA%d" },
{ SEVERITY_ERR, MSG_INVPGA, PARAM_PGAMAX, "Invalid PGA id %d - range is
0 - %d" },
{ SEVERITY_INFO, MSG_PGALRENA,PARAM_PGA, "PGA %d already enabled" },
{ SEVERITY_INFO, MSG_PGALRDIS,PARAM_PGA, "PGA %d already disabled" },
{ SEVERITY_INFO, MSG_PGAENA, PARAM_PGA, "PGA %d sent enable message" },
{ SEVERITY_INFO, MSG_PGADIS, PARAM_PGA, "PGA %d set disable flag" },
{ SEVERITY_ERR, MSG_PGAUNW, PARAM_PGA, "PGA %d is not flagged WELL, cannot
enable" },
#endif
{ SEVERITY_SUCC, MSG_NUMPGA, PARAM_NONE, "PGA count" },
{ SEVERITY_SUCC, MSG_NUMASC, PARAM_NONE, "ASC count" },
{ SEVERITY_SUCC, MSG_VERSION, PARAM_NONE, "CGMiner versions" },
{ SEVERITY_ERR, MSG_INVJSON, PARAM_NONE, "Invalid JSON" },
{ SEVERITY_ERR, MSG_MISCMD, PARAM_CMD, "Missing JSON '%s'" },
{ SEVERITY_ERR, MSG_MISPID, PARAM_NONE, "Missing pool id parameter" },
{ SEVERITY_ERR, MSG_INVPID, PARAM_POOLMAX, "Invalid pool id %d - range
is 0 - %d" },
{ SEVERITY_SUCC, MSG_SWITCHP, PARAM_POOL, "Switching to pool %d:'%s'" },
{ SEVERITY_SUCC, MSG_MINECONFIG,PARAM_NONE, "CGMiner config" },
{ SEVERITY_ERR, MSG_MISFN, PARAM_NONE, "Missing save filename parameter" },
{ SEVERITY_ERR, MSG_BADFN, PARAM_STR, "Can't open or create save file '%s'" },
{ SEVERITY_SUCC, MSG_SAVED, PARAM_STR, "Configuration saved to file '%s'" },
{ SEVERITY_ERR, MSG_ACCDENY, PARAM_STR, "Access denied to '%s' command" },
{ SEVERITY_SUCC, MSG_ACCOK, PARAM_NONE, "Privileged access OK" },
{ SEVERITY_SUCC, MSG_ENAPOOL, PARAM_POOL, "Enabling pool %d:'%s'" },
{ SEVERITY_SUCC, MSG_POOLPRIO,PARAM_NONE, "Changed pool priorities" },
{ SEVERITY_ERR, MSG_DUPPID, PARAM_PID, "Duplicate pool specified %d" },
{ SEVERITY_SUCC, MSG_DISPOOL, PARAM_POOL, "Disabling pool %d:'%s'" },
{ SEVERITY_INFO, MSG_ALRENAP, PARAM_POOL, "Pool %d:'%s' already enabled" },
{ SEVERITY_INFO, MSG_ALRDISP, PARAM_POOL, "Pool %d:'%s' already disabled" },
{ SEVERITY_ERR, MSG_DISLASTP,PARAM_POOL, "Cannot disable last active pool
%d:'%s'" },
{ SEVERITY_ERR, MSG_MISPDP, PARAM_NONE, "Missing addpool details" },
{ SEVERITY_ERR, MSG_INVPDP, PARAM_STR, "Invalid addpool details '%s'" },
{ SEVERITY_ERR, MSG_TOOMANYP,PARAM_NONE, "Reached maximum number of pools
(%d)" },
{ SEVERITY_SUCC, MSG_ADDPOOL, PARAM_POOL, "Added pool %d: '%s'" },
{ SEVERITY_ERR, MSG_REMLASTP,PARAM_POOL, "Cannot remove last pool
%d:'%s'" },
{ SEVERITY_ERR, MSG_ACTPOOL, PARAM_POOL, "Cannot remove active pool %d:'%s'"
},
{ SEVERITY_SUCC, MSG_REMPOOL, PARAM_BOTH, "Removed pool %d:'%s'" },
{ SEVERITY_SUCC, MSG_NOTIFY, PARAM_NONE, "Notify" },
{ SEVERITY_SUCC, MSG_DEVDETAILS,PARAM_NONE, "Device Details" },
{ 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_ERR, MSG_MISBOOL, PARAM_NONE, "Missing parameter: true/false" },
{ SEVERITY_ERR, MSG_INVBOOL, PARAM_NONE, "Invalid parameter should be true
or false" },
{ SEVERITY_SUCC, MSG_FOO, PARAM_BOOL, "Failover-Only set to %s" },
{ SEVERITY_SUCC, MSG_MINECOIN,PARAM_NONE, "CGMiner coin" },
{ SEVERITY_SUCC, MSG_DEBUGSET,PARAM_NONE, "Debug settings" },
#ifdef HAVE_AN_FPGA
{ SEVERITY_SUCC, MSG_PGAIDENT,PARAM_PGA, "Identify command sent to PGA%d" },
{ SEVERITY_WARN, MSG_PGANOID, PARAM_PGA, "PGA%d does not support
identify" },
#endif
{ SEVERITY_SUCC, MSG_SETCONFIG,PARAM_SET, "Set config '%s' to %d" },
{ SEVERITY_ERR, MSG_UNKCON, PARAM_STR, "Unknown config '%s'" },
{ SEVERITY_ERR, MSG_DEPRECATED, PARAM_STR, "Deprecated config option '%s'" },
{ SEVERITY_ERR, MSG_INVNUM, PARAM_BOTH, "Invalid number (%d) for '%s' range
is 0-9999" },
{ SEVERITY_ERR, MSG_INVNEG, PARAM_BOTH, "Invalid negative number (%d) for
'%s'" },
{ 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_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" },
#ifdef HAVE_AN_FPGA
{ SEVERITY_ERR, MSG_MISPGAOPT, PARAM_NONE, "Missing option after PGA
number" },
{ SEVERITY_WARN, MSG_PGANOSET, PARAM_PGA, "PGA %d does not support pgaset" },
{ SEVERITY_INFO, MSG_PGAHELP, PARAM_BOTH, "PGA %d set help: %s" },
{ SEVERITY_SUCC, MSG_PGASETOK, PARAM_BOTH, "PGA %d set OK" },
{ SEVERITY_ERR, MSG_PGASETERR, PARAM_BOTH, "PGA %d set failed: %s" },
#endif
{ SEVERITY_ERR, MSG_ZERMIS, PARAM_NONE, "Missing zero parameters" },
{ SEVERITY_ERR, MSG_ZERINV, PARAM_STR, "Invalid zero parameter '%s'" },
{ SEVERITY_SUCC, MSG_ZERSUM, PARAM_STR, "Zeroed %s stats with summary" },
{ SEVERITY_SUCC, MSG_ZERNOSUM, PARAM_STR, "Zeroed %s stats without
summary" },
#ifdef USE_USBUTILS
{ SEVERITY_ERR, MSG_PGAUSBNODEV, PARAM_PGA, "PGA%d has no device" },
{ SEVERITY_ERR, MSG_ASCUSBNODEV, PARAM_PGA, "ASC%d has no device" },
#endif
{ SEVERITY_ERR, MSG_INVHPLG, PARAM_STR, "Invalid value for hotplug (%s)
must be 0..9999" },
{ SEVERITY_SUCC, MSG_HOTPLUG, PARAM_INT, "Hotplug check set to %ds" },
{ SEVERITY_SUCC, MSG_DISHPLG, PARAM_NONE, "Hotplug disabled" },
{ SEVERITY_WARN, MSG_NOHPLG, PARAM_NONE, "Hotplug is not available" },
{ SEVERITY_ERR, MSG_MISHPLG, PARAM_NONE, "Missing hotplug parameter" },
#ifdef HAVE_AN_ASIC
{ SEVERITY_ERR, MSG_ASCNON, PARAM_NONE, "No ASCs" },
{ SEVERITY_SUCC, MSG_ASCDEV, PARAM_ASC, "ASC%d" },
{ SEVERITY_ERR, MSG_INVASC, PARAM_ASCMAX, "Invalid ASC id %d - range is
0 - %d" },
{ SEVERITY_INFO, MSG_ASCLRENA,PARAM_ASC, "ASC %d already enabled" },
{ SEVERITY_INFO, MSG_ASCLRDIS,PARAM_ASC, "ASC %d already disabled" },
{ SEVERITY_INFO, MSG_ASCENA, PARAM_ASC, "ASC %d sent enable message" },
{ SEVERITY_INFO, MSG_ASCDIS, PARAM_ASC, "ASC %d set disable flag" },
{ SEVERITY_ERR, MSG_ASCUNW, PARAM_ASC, "ASC %d is not flagged WELL, cannot
enable" },
{ SEVERITY_SUCC, MSG_ASCIDENT,PARAM_ASC, "Identify command sent to ASC%d" },
{ SEVERITY_WARN, MSG_ASCNOID, PARAM_ASC, "ASC%d does not support
identify" },
{ SEVERITY_ERR, MSG_MISASCOPT, PARAM_NONE, "Missing option after ASC
number" },
{ SEVERITY_WARN, MSG_ASCNOSET, PARAM_ASC, "ASC %d does not support ascset" },
{ SEVERITY_INFO, MSG_ASCHELP, PARAM_BOTH, "ASC %d set help: %s" },
{ SEVERITY_SUCC, MSG_ASCSETOK, PARAM_BOTH, "ASC %d set OK" },
{ SEVERITY_ERR, MSG_ASCSETERR, PARAM_BOTH, "ASC %d set failed: %s" },
#endif
{ SEVERITY_SUCC, MSG_LCD, PARAM_NONE, "LCD" },
{ SEVERITY_SUCC, MSG_LOCKOK, PARAM_NONE, "Lock stats created" },
{ SEVERITY_WARN, MSG_LOCKDIS, PARAM_NONE, "Lock stats not enabled" },
{ SEVERITY_FAIL, 0, 0, NULL }
};

static const char *localaddr = "127.0.0.1";

static int my_thr_id = 0;


static bool bye;

// Used to control quit restart access to shutdown variables


static pthread_mutex_t quit_restart_lock;

static bool do_a_quit;


static bool do_a_restart;

static time_t when = 0; // when the request occurred

struct IPACCESS {
struct in6_addr ip;
struct in6_addr mask;
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 IPACCESS *ipaccess = NULL;


static int ips = 0;

struct io_data {
size_t siz;
char *ptr;
char *cur;
bool sock;
bool close;
};

struct io_list {
struct io_data *io_data;
struct io_list *prev;
struct io_list *next;
};

static struct io_list *io_head = NULL;

#define SOCKBUFALLOCSIZ 65536

#define io_new(init) _io_new(init, false)


#define sock_io_new() _io_new(SOCKBUFALLOCSIZ, true)
#define ALLOC_SBITEMS 2
#define LIMIT_SBITEMS 0

typedef struct sbitem {


char *buf;
size_t siz;
size_t tot;
} SBITEM;

// Size to grow tot if exceeded


#define SBEXTEND 4096

#define DATASB(_item) ((SBITEM *)(_item->data))

static K_LIST *strbufs;

static void io_reinit(struct io_data *io_data)


{
io_data->cur = io_data->ptr;
*(io_data->ptr) = '\0';
io_data->close = false;
}

static struct io_data *_io_new(size_t initial, bool socket_buf)


{
struct io_data *io_data;
struct io_list *io_list;

io_data = cgmalloc(sizeof(*io_data));
io_data->ptr = cgmalloc(initial);
io_data->siz = initial;
io_data->sock = socket_buf;
io_reinit(io_data);

io_list = cgmalloc(sizeof(*io_list));

io_list->io_data = io_data;

if (io_head) {
io_list->next = io_head;
io_list->prev = io_head->prev;
io_list->next->prev = io_list;
io_list->prev->next = io_list;
} else {
io_list->prev = io_list;
io_list->next = io_list;
io_head = io_list;
}

return io_data;
}

static bool io_add(struct io_data *io_data, char *buf)


{
size_t len, dif, tot;

len = strlen(buf);
dif = io_data->cur - io_data->ptr;
// send will always have enough space to add the JSON
tot = len + 1 + dif + sizeof(JSON_CLOSE) + sizeof(JSON_END);

if (tot > io_data->siz) {


size_t new = io_data->siz + (2 * SOCKBUFALLOCSIZ);

if (new < tot)


new = (2 + (size_t)((float)tot / (float)SOCKBUFALLOCSIZ)) *
SOCKBUFALLOCSIZ;

io_data->ptr = cgrealloc(io_data->ptr, new);


io_data->cur = io_data->ptr + dif;
io_data->siz = new;
}

memcpy(io_data->cur, buf, len + 1);


io_data->cur += len;

return true;
}

static bool io_put(struct io_data *io_data, char *buf)


{
io_reinit(io_data);
return io_add(io_data, buf);
}

static void io_close(struct io_data *io_data)


{
io_data->close = true;
}

static void io_free()


{
struct io_list *io_list, *io_next;

if (io_head) {
io_list = io_head;
do {
io_next = io_list->next;

free(io_list->io_data->ptr);
free(io_list->io_data);
free(io_list);

io_list = io_next;
} while (io_list != io_head);

io_head = NULL;
}
}

// This is only called when expected to be needed (rarely)


// i.e. strings outside of the codes control (input from the user)
static char *escape_string(char *str, bool isjson)
{
char *buf, *ptr;
int count;
count = 0;
for (ptr = str; *ptr; ptr++) {
switch (*ptr) {
case ',':
case '|':
case '=':
if (!isjson)
count++;
break;
case '"':
if (isjson)
count++;
break;
case '\\':
count++;
break;
}
}

if (count == 0)
return str;

buf = cgmalloc(strlen(str) + count + 1);

ptr = buf;
while (*str)
switch (*str) {
case ',':
case '|':
case '=':
if (!isjson)
*(ptr++) = '\\';
*(ptr++) = *(str++);
break;
case '"':
if (isjson)
*(ptr++) = '\\';
*(ptr++) = *(str++);
break;
case '\\':
*(ptr++) = '\\';
*(ptr++) = *(str++);
break;
default:
*(ptr++) = *(str++);
break;
}

*ptr = '\0';

return buf;
}

static struct api_data *api_add_extra(struct api_data *root, struct api_data


*extra)
{
struct api_data *tmp;

if (root) {
if (extra) {
// extra tail
tmp = extra->prev;

// extra prev = root tail


extra->prev = root->prev;

// root tail next = extra


root->prev->next = extra;

// extra tail next = root


tmp->next = root;

// root prev = extra tail


root->prev = tmp;
}
} else
root = extra;

return root;
}

static struct api_data *api_add_data_full(struct api_data *root, char *name, enum


api_data_type type, void *data, bool copy_data)
{
struct api_data *api_data;

api_data = cgmalloc(sizeof(struct api_data));

api_data->name = strdup(name);
api_data->type = type;

if (root == NULL) {
root = api_data;
root->prev = root;
root->next = root;
} else {
api_data->prev = root->prev;
root->prev = api_data;
api_data->next = root;
api_data->prev->next = api_data;
}

api_data->data_was_malloc = copy_data;

// Avoid crashing on bad data


if (data == NULL) {
api_data->type = type = API_CONST;
data = (void *)NULLSTR;
api_data->data_was_malloc = copy_data = false;
}

if (!copy_data)
api_data->data = data;
else
switch(type) {
case API_ESCAPE:
case API_STRING:
case API_CONST:
api_data->data = cgmalloc(strlen((char *)data) + 1);
strcpy((char*)(api_data->data), (char *)data);
break;
case API_UINT8:
/* Most OSs won't really alloc less than 4 */
api_data->data = cgmalloc(4);
*(uint8_t *)api_data->data = *(uint8_t *)data;
break;
case API_INT16:
/* Most OSs won't really alloc less than 4 */
api_data->data = cgmalloc(4);
*(int16_t *)api_data->data = *(int16_t *)data;
break;
case API_UINT16:
/* Most OSs won't really alloc less than 4 */
api_data->data = cgmalloc(4);
*(uint16_t *)api_data->data = *(uint16_t *)data;
break;
case API_INT:
api_data->data = cgmalloc(sizeof(int));
*((int *)(api_data->data)) = *((int *)data);
break;
case API_UINT:
api_data->data = cgmalloc(sizeof(unsigned int));
*((unsigned int *)(api_data->data)) = *((unsigned int
*)data);
break;
case API_UINT32:
api_data->data = cgmalloc(sizeof(uint32_t));
*((uint32_t *)(api_data->data)) = *((uint32_t *)data);
break;
case API_HEX32:
api_data->data = cgmalloc(sizeof(uint32_t));
*((uint32_t *)(api_data->data)) = *((uint32_t *)data);
break;
case API_UINT64:
api_data->data = cgmalloc(sizeof(uint64_t));
*((uint64_t *)(api_data->data)) = *((uint64_t *)data);
break;
case API_INT64:
api_data->data = cgmalloc(sizeof(int64_t));
*((int64_t *)(api_data->data)) = *((int64_t *)data);
break;
case API_DOUBLE:
case API_ELAPSED:
case API_MHS:
case API_MHTOTAL:
case API_UTILITY:
case API_FREQ:
case API_HS:
case API_DIFF:
case API_PERCENT:
api_data->data = cgmalloc(sizeof(double));
*((double *)(api_data->data)) = *((double *)data);
break;
case API_BOOL:
api_data->data = cgmalloc(sizeof(bool));
*((bool *)(api_data->data)) = *((bool *)data);
break;
case API_TIMEVAL:
api_data->data = cgmalloc(sizeof(struct timeval));
memcpy(api_data->data, data, sizeof(struct timeval));
break;
case API_TIME:
api_data->data = cgmalloc(sizeof(time_t));
*(time_t *)(api_data->data) = *((time_t *)data);
break;
case API_VOLTS:
case API_TEMP:
case API_AVG:
api_data->data = cgmalloc(sizeof(float));
*((float *)(api_data->data)) = *((float *)data);
break;
default:
applog(LOG_ERR, "API: unknown1 data type %d ignored",
type);
api_data->type = API_STRING;
api_data->data_was_malloc = false;
api_data->data = (void *)UNKNOWN;
break;
}

return root;
}

struct api_data *api_add_escape(struct api_data *root, char *name, char *data, bool
copy_data)
{
return api_add_data_full(root, name, API_ESCAPE, (void *)data, copy_data);
}

struct api_data *api_add_string(struct api_data *root, char *name, char *data, bool
copy_data)
{
return api_add_data_full(root, name, API_STRING, (void *)data, copy_data);
}

struct api_data *api_add_const(struct api_data *root, char *name, const char *data,
bool copy_data)
{
return api_add_data_full(root, name, API_CONST, (void *)data, copy_data);
}

struct api_data *api_add_uint8(struct api_data *root, char *name, uint8_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_UINT8, (void *)data, copy_data);
}

struct api_data *api_add_int16(struct api_data *root, char *name, uint16_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_INT16, (void *)data, copy_data);
}

struct api_data *api_add_uint16(struct api_data *root, char *name, uint16_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_UINT16, (void *)data, copy_data);
}

struct api_data *api_add_int(struct api_data *root, char *name, int *data, bool
copy_data)
{
return api_add_data_full(root, name, API_INT, (void *)data, copy_data);
}

struct api_data *api_add_uint(struct api_data *root, char *name, unsigned int


*data, bool copy_data)
{
return api_add_data_full(root, name, API_UINT, (void *)data, copy_data);
}

struct api_data *api_add_uint32(struct api_data *root, char *name, uint32_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_UINT32, (void *)data, copy_data);
}

struct api_data *api_add_hex32(struct api_data *root, char *name, uint32_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_HEX32, (void *)data, copy_data);
}

struct api_data *api_add_uint64(struct api_data *root, char *name, uint64_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_UINT64, (void *)data, copy_data);
}

struct api_data *api_add_int64(struct api_data *root, char *name, int64_t *data,


bool copy_data)
{
return api_add_data_full(root, name, API_INT64, (void *)data, copy_data);
}

struct api_data *api_add_double(struct api_data *root, char *name, double *data,


bool copy_data)
{
return api_add_data_full(root, name, API_DOUBLE, (void *)data, copy_data);
}

struct api_data *api_add_elapsed(struct api_data *root, char *name, double *data,


bool copy_data)
{
return api_add_data_full(root, name, API_ELAPSED, (void *)data, copy_data);
}

struct api_data *api_add_bool(struct api_data *root, char *name, bool *data, bool
copy_data)
{
return api_add_data_full(root, name, API_BOOL, (void *)data, copy_data);
}

struct api_data *api_add_timeval(struct api_data *root, char *name, struct timeval


*data, bool copy_data)
{
return api_add_data_full(root, name, API_TIMEVAL, (void *)data, copy_data);
}

struct api_data *api_add_time(struct api_data *root, char *name, time_t *data, bool
copy_data)
{
return api_add_data_full(root, name, API_TIME, (void *)data, copy_data);
}

struct api_data *api_add_mhs(struct api_data *root, char *name, double *data, bool
copy_data)
{
return api_add_data_full(root, name, API_MHS, (void *)data, copy_data);
}

struct api_data *api_add_mhtotal(struct api_data *root, char *name, double *data,


bool copy_data)
{
return api_add_data_full(root, name, API_MHTOTAL, (void *)data, copy_data);
}

struct api_data *api_add_temp(struct api_data *root, char *name, float *data, bool
copy_data)
{
return api_add_data_full(root, name, API_TEMP, (void *)data, copy_data);
}

struct api_data *api_add_utility(struct api_data *root, char *name, double *data,


bool copy_data)
{
return api_add_data_full(root, name, API_UTILITY, (void *)data, copy_data);
}

struct api_data *api_add_freq(struct api_data *root, char *name, double *data, bool
copy_data)
{
return api_add_data_full(root, name, API_FREQ, (void *)data, copy_data);
}

struct api_data *api_add_volts(struct api_data *root, char *name, float *data, bool
copy_data)
{
return api_add_data_full(root, name, API_VOLTS, (void *)data, copy_data);
}

struct api_data *api_add_hs(struct api_data *root, char *name, double *data, bool
copy_data)
{
return api_add_data_full(root, name, API_HS, (void *)data, copy_data);
}

struct api_data *api_add_diff(struct api_data *root, char *name, double *data, bool
copy_data)
{
return api_add_data_full(root, name, API_DIFF, (void *)data, copy_data);
}

struct api_data *api_add_percent(struct api_data *root, char *name, double *data,


bool copy_data)
{
return api_add_data_full(root, name, API_PERCENT, (void *)data, copy_data);
}

struct api_data *api_add_avg(struct api_data *root, char *name, float *data, bool
copy_data)
{
return api_add_data_full(root, name, API_AVG, (void *)data, copy_data);
}

static void add_item_buf(K_ITEM *item, const char *str)


{
size_t old_siz, new_siz, siz, ext;
char *buf;

buf = DATASB(item)->buf;
siz = (size_t)strlen(str);

old_siz = DATASB(item)->siz;
new_siz = old_siz + siz + 1; // include '\0'
if (DATASB(item)->tot < new_siz) {
ext = (siz + 1) + SBEXTEND - ((siz + 1) % SBEXTEND);
DATASB(item)->buf = buf = cgrealloc(DATASB(item)->buf, DATASB(item)-
>tot + ext);
DATASB(item)->tot += ext;
}
memcpy(buf + old_siz, str, siz + 1);
DATASB(item)->siz += siz;
}

static struct api_data *print_data(struct io_data *io_data, struct api_data *root,


bool isjson, bool precom)
{
// N.B. strings don't use this buffer so 64 is enough (for now)
char buf[64];
struct api_data *tmp;
bool done, first = true;
char *original, *escape;
K_ITEM *item;

K_WLOCK(strbufs);
item = k_unlink_head(strbufs);
K_WUNLOCK(strbufs);

DATASB(item)->siz = 0;

if (precom)
add_item_buf(item, COMMA);

if (isjson)
add_item_buf(item, JSON0);

while (root) {
if (!first)
add_item_buf(item, COMMA);
else
first = false;
if (isjson)
add_item_buf(item, JSON1);

add_item_buf(item, root->name);

if (isjson)
add_item_buf(item, JSON1);

if (isjson)
add_item_buf(item, ":");
else
add_item_buf(item, "=");

first = false;

done = false;
switch(root->type) {
case API_STRING:
case API_CONST:
if (isjson)
add_item_buf(item, JSON1);
add_item_buf(item, (char *)(root->data));
if (isjson)
add_item_buf(item, JSON1);
done = true;
break;
case API_ESCAPE:
original = (char *)(root->data);
escape = escape_string((char *)(root->data), isjson);
if (isjson)
add_item_buf(item, JSON1);
add_item_buf(item, escape);
if (isjson)
add_item_buf(item, JSON1);
if (escape != original)
free(escape);
done = true;
break;
case API_UINT8:
snprintf(buf, sizeof(buf), "%u", *(uint8_t *)root->data);
break;
case API_INT16:
snprintf(buf, sizeof(buf), "%d", *(int16_t *)root->data);
break;
case API_UINT16:
snprintf(buf, sizeof(buf), "%u", *(uint16_t *)root->data);
break;
case API_INT:
snprintf(buf, sizeof(buf), "%d", *((int *)(root->data)));
break;
case API_UINT:
snprintf(buf, sizeof(buf), "%u", *((unsigned int *)(root-
>data)));
break;
case API_UINT32:
snprintf(buf, sizeof(buf), "%"PRIu32, *((uint32_t *)(root-
>data)));
break;
case API_HEX32:
if (isjson)
add_item_buf(item, JSON1);
snprintf(buf, sizeof(buf), "0x%08x", *((uint32_t *)(root-
>data)));
add_item_buf(item, buf);
if (isjson)
add_item_buf(item, JSON1);
done = true;
break;
case API_UINT64:
snprintf(buf, sizeof(buf), "%"PRIu64, *((uint64_t *)(root-
>data)));
break;
case API_INT64:
snprintf(buf, sizeof(buf), "%"PRId64, *((int64_t *)(root-
>data)));
break;
case API_TIME:
snprintf(buf, sizeof(buf), "%lu", *((unsigned long *)(root-
>data)));
break;
case API_DOUBLE:
snprintf(buf, sizeof(buf), "%f", *((double *)(root-
>data)));
break;
case API_ELAPSED:
snprintf(buf, sizeof(buf), "%.0f", *((double *)(root-
>data)));
break;
case API_UTILITY:
case API_FREQ:
case API_MHS:
snprintf(buf, sizeof(buf), "%.2f", *((double *)(root-
>data)));
break;
case API_VOLTS:
case API_AVG:
snprintf(buf, sizeof(buf), "%.3f", *((float *)(root-
>data)));
break;
case API_MHTOTAL:
snprintf(buf, sizeof(buf), "%.4f", *((double *)(root-
>data)));
break;
case API_HS:
snprintf(buf, sizeof(buf), "%.15f", *((double *)(root-
>data)));
break;
case API_DIFF:
snprintf(buf, sizeof(buf), "%.8f", *((double *)(root-
>data)));
break;
case API_BOOL:
snprintf(buf, sizeof(buf), "%s", *((bool *)(root->data)) ?
TRUESTR : FALSESTR);
break;
case API_TIMEVAL:
snprintf(buf, sizeof(buf), "%ld.%06ld",
(long)((struct timeval *)(root->data))->tv_sec,
(long)((struct timeval *)(root->data))->tv_usec);
break;
case API_TEMP:
snprintf(buf, sizeof(buf), "%.2f", *((float *)(root-
>data)));
break;
case API_PERCENT:
snprintf(buf, sizeof(buf), "%.4f", *((double *)(root-
>data)) * 100.0);
break;
default:
applog(LOG_ERR, "API: unknown2 data type %d ignored", root-
>type);
if (isjson)
add_item_buf(item, JSON1);
add_item_buf(item, UNKNOWN);
if (isjson)
add_item_buf(item, JSON1);
done = true;
break;
}

if (!done)
add_item_buf(item, buf);

free(root->name);
if (root->data_was_malloc)
free(root->data);

if (root->next == root) {
free(root);
root = NULL;
} else {
tmp = root;
root = tmp->next;
root->prev = tmp->prev;
root->prev->next = root;
free(tmp);
}
}

if (isjson)
add_item_buf(item, JSON5);
else
add_item_buf(item, SEPSTR);

io_add(io_data, DATASB(item)->buf);

K_WLOCK(strbufs);
k_add_head(strbufs, item);
K_WUNLOCK(strbufs);

return root;
}

#define DRIVER_COUNT_DRV(X) if (devices[i]->drv->drv_id == DRIVER_##X) \


count++;

#ifdef HAVE_AN_ASIC
static int numascs(void)
{
int count = 0;
int i;

rd_lock(&devices_lock);
for (i = 0; i < total_devices; i++) {
ASIC_PARSE_COMMANDS(DRIVER_COUNT_DRV)
}
rd_unlock(&devices_lock);
return count;
}

static int ascdevice(int ascid)


{
int count = 0;
int i;

rd_lock(&devices_lock);
for (i = 0; i < total_devices; i++) {
ASIC_PARSE_COMMANDS(DRIVER_COUNT_DRV)
if (count == (ascid + 1))
goto foundit;
}

rd_unlock(&devices_lock);
return -1;

foundit:

rd_unlock(&devices_lock);
return i;
}
#endif

#ifdef HAVE_AN_FPGA
static int numpgas(void)
{
int count = 0;
int i;

rd_lock(&devices_lock);
for (i = 0; i < total_devices; i++) {
FPGA_PARSE_COMMANDS(DRIVER_COUNT_DRV)
}
rd_unlock(&devices_lock);
return count;
}

static int pgadevice(int pgaid)


{
int count = 0;
int i;

rd_lock(&devices_lock);
for (i = 0; i < total_devices; i++) {
FPGA_PARSE_COMMANDS(DRIVER_COUNT_DRV)
if (count == (pgaid + 1))
goto foundit;
}

rd_unlock(&devices_lock);
return -1;

foundit:

rd_unlock(&devices_lock);
return i;
}
#endif

#define LIMSIZ (TMPBUFSIZ - 1)

// All replies (except BYE and RESTART) start with a message


// thus for JSON, message() inserts JSON_START at the front
// and send_result() adds JSON_END at the end
static void message(struct io_data *io_data, int messageid, int paramid, char
*param2, bool isjson)
{
struct api_data *root = NULL;
char buf[TMPBUFSIZ];
char severity[2];
#ifdef HAVE_AN_ASIC
int asc;
#endif
#ifdef HAVE_AN_FPGA
int pga;
#endif
int i;

if (isjson)
io_add(io_data, JSON_START JSON_STATUS);

for (i = 0; codes[i].severity != SEVERITY_FAIL; i++) {


if (codes[i].code == messageid) {
switch (codes[i].severity) {
case SEVERITY_WARN:
severity[0] = 'W';
break;
case SEVERITY_INFO:
severity[0] = 'I';
break;
case SEVERITY_SUCC:
severity[0] = 'S';
break;
case SEVERITY_ERR:
default:
severity[0] = 'E';
break;
}
severity[1] = '\0';

switch(codes[i].params) {
case PARAM_PGA:
case PARAM_ASC:
case PARAM_PID:
case PARAM_INT:
snprintf(buf, LIMSIZ, codes[i].description, paramid);
break;
case PARAM_POOL:
snprintf(buf, LIMSIZ, codes[i].description, paramid,
pools[paramid]->rpc_url);
break;
#ifdef HAVE_AN_FPGA
case PARAM_PGAMAX:
pga = numpgas();
snprintf(buf, LIMSIZ, codes[i].description, paramid,
pga - 1);
break;
#endif
#ifdef HAVE_AN_ASIC
case PARAM_ASCMAX:
asc = numascs();
snprintf(buf, LIMSIZ, codes[i].description, paramid,
asc - 1);
break;
#endif
case PARAM_PMAX:
snprintf(buf, LIMSIZ, codes[i].description,
total_pools);
break;
case PARAM_POOLMAX:
snprintf(buf, LIMSIZ, codes[i].description, paramid,
total_pools - 1);
break;
case PARAM_DMAX:
#ifdef HAVE_AN_ASIC
asc = numascs();
#endif
#ifdef HAVE_AN_FPGA
pga = numpgas();
#endif

snprintf(buf, LIMSIZ, codes[i].description


#ifdef HAVE_AN_ASIC
, asc
#endif
#ifdef HAVE_AN_FPGA
, pga
#endif
);
break;
case PARAM_CMD:
snprintf(buf, LIMSIZ, codes[i].description,
JSON_COMMAND);
break;
case PARAM_STR:
snprintf(buf, LIMSIZ, codes[i].description, param2);
break;
case PARAM_BOTH:
snprintf(buf, LIMSIZ, codes[i].description, paramid,
param2);
break;
case PARAM_BOOL:
snprintf(buf, LIMSIZ, codes[i].description, paramid ?
TRUESTR : FALSESTR);
break;
case PARAM_SET:
snprintf(buf, LIMSIZ, codes[i].description, param2,
paramid);
break;
case PARAM_NONE:
default:
strcpy(buf, codes[i].description);
}

root = api_add_string(root, _STATUS, severity, false);


root = api_add_time(root, "When", &when, false);
root = api_add_int(root, "Code", &messageid, false);
root = api_add_escape(root, "Msg", buf, false);
/* Do not give out description for random probes to
* addresses with inappropriately open API ports. */
if (messageid != MSG_INVCMD)
root = api_add_escape(root, "Description",
opt_api_description, false);

root = print_data(io_data, root, isjson, false);


if (isjson)
io_add(io_data, JSON_CLOSE);
return;
}
}

root = api_add_string(root, _STATUS, "F", false);


root = api_add_time(root, "When", &when, false);
int id = -1;
root = api_add_int(root, "Code", &id, false);
sprintf(buf, "%d", messageid);
root = api_add_escape(root, "Msg", buf, false);
root = api_add_escape(root, "Description", opt_api_description, false);

root = print_data(io_data, root, isjson, false);


if (isjson)
io_add(io_data, JSON_CLOSE);
}

#if LOCK_TRACKING

#define LOCK_FMT_FFL " - called from %s %s():%d"

#define LOCKMSG(fmt, ...) fprintf(stderr, "APILOCK: " fmt "\n", ##__VA_ARGS__)


#define LOCKMSGMORE(fmt, ...) fprintf(stderr, " " fmt "\n", ##__VA_ARGS__)
#define LOCKMSGFFL(fmt, ...) fprintf(stderr, "APILOCK: " fmt LOCK_FMT_FFL "\n",
##__VA_ARGS__, file, func, linenum)
#define LOCKMSGFLUSH() fflush(stderr)

typedef struct lockstat {


uint64_t lock_id;
const char *file;
const char *func;
int linenum;
struct timeval tv;
} LOCKSTAT;

typedef struct lockline {


struct lockline *prev;
struct lockstat *stat;
struct lockline *next;
} LOCKLINE;

typedef struct lockinfo {


void *lock;
enum cglock_typ typ;
const char *file;
const char *func;
int linenum;
uint64_t gets;
uint64_t gots;
uint64_t tries;
uint64_t dids;
uint64_t didnts; // should be tries - dids
uint64_t unlocks;
LOCKSTAT lastgot;
LOCKLINE *lockgets;
LOCKLINE *locktries;
} LOCKINFO;

typedef struct locklist {


LOCKINFO *info;
struct locklist *next;
} LOCKLIST;

static uint64_t lock_id = 1;

static LOCKLIST *lockhead;

static void lockmsgnow()


{
struct timeval now;
struct tm *tm;
time_t dt;

cgtime(&now);

dt = now.tv_sec;
tm = localtime(&dt);

LOCKMSG("%d-%02d-%02d %02d:%02d:%02d",
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
}

static LOCKLIST *newlock(void *lock, enum cglock_typ typ, const char *file, const
char *func, const int linenum)
{
LOCKLIST *list;

list = cgcalloc(1, sizeof(*list));


list->info = cgcalloc(1, sizeof(*(list->info)));
list->next = lockhead;
lockhead = list;
list->info->lock = lock;
list->info->typ = typ;
list->info->file = file;
list->info->func = func;
list->info->linenum = linenum;

return list;
}

static LOCKINFO *findlock(void *lock, enum cglock_typ typ, const char *file, const
char *func, const int linenum)
{
LOCKLIST *look;

look = lockhead;
while (look) {
if (look->info->lock == lock)
break;
look = look->next;
}

if (!look)
look = newlock(lock, typ, file, func, linenum);

return look->info;
}

static void addgettry(LOCKINFO *info, uint64_t id, const char *file, const char
*func, const int linenum, bool get)
{
LOCKSTAT *stat;
LOCKLINE *line;

stat = cgcalloc(1, sizeof(*stat));


line = cgcalloc(1, sizeof(*line));

if (get)
info->gets++;
else
info->tries++;

stat->lock_id = id;
stat->file = file;
stat->func = func;
stat->linenum = linenum;
cgtime(&stat->tv);

line->stat = stat;

if (get) {
line->next = info->lockgets;
if (info->lockgets)
info->lockgets->prev = line;
info->lockgets = line;
} else {
line->next = info->locktries;
if (info->locktries)
info->locktries->prev = line;
info->locktries = line;
}
}

static void markgotdid(LOCKINFO *info, uint64_t id, const char *file, const char
*func, const int linenum, bool got, int ret)
{
LOCKLINE *line;

if (got)
info->gots++;
else {
if (ret == 0)
info->dids++;
else
info->didnts++;
}

if (got || ret == 0) {
info->lastgot.lock_id = id;
info->lastgot.file = file;
info->lastgot.func = func;
info->lastgot.linenum = linenum;
cgtime(&info->lastgot.tv);
}

if (got)
line = info->lockgets;
else
line = info->locktries;
while (line) {
if (line->stat->lock_id == id)
break;
line = line->next;
}

if (!line) {
lockmsgnow();
LOCKMSGFFL("ERROR attempt to mark a lock as '%s' that wasn't '%s' id=
%"PRIu64,
got ? "got" : "did/didnt", got ? "get" : "try", id);
}

// Unlink it
if (line->prev)
line->prev->next = line->next;
if (line->next)
line->next->prev = line->prev;

if (got) {
if (info->lockgets == line)
info->lockgets = line->next;
} else {
if (info->locktries == line)
info->locktries = line->next;
}

free(line->stat);
free(line);
}

// Yes this uses locks also ... ;/


static void locklock()
{
if (unlikely(pthread_mutex_lock(&lockstat_lock)))
quithere(1, "WTF MUTEX ERROR ON LOCK! errno=%d", errno);
}

static void lockunlock()


{
if (unlikely(pthread_mutex_unlock(&lockstat_lock)))
quithere(1, "WTF MUTEX ERROR ON UNLOCK! errno=%d", errno);
}

uint64_t api_getlock(void *lock, const char *file, const char *func, const int
linenum)
{
LOCKINFO *info;
uint64_t id;

locklock();

info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);


id = lock_id++;
addgettry(info, id, file, func, linenum, true);

lockunlock();

return id;
}

void api_gotlock(uint64_t id, void *lock, const char *file, const char *func, const
int linenum)
{
LOCKINFO *info;

locklock();

info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);


markgotdid(info, id, file, func, linenum, true, 0);

lockunlock();
}

uint64_t api_trylock(void *lock, const char *file, const char *func, const int
linenum)
{
LOCKINFO *info;
uint64_t id;

locklock();

info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);


id = lock_id++;
addgettry(info, id, file, func, linenum, false);

lockunlock();
return id;
}

void api_didlock(uint64_t id, int ret, void *lock, const char *file, const char
*func, const int linenum)
{
LOCKINFO *info;

locklock();

info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);


markgotdid(info, id, file, func, linenum, false, ret);

lockunlock();
}

void api_gunlock(void *lock, const char *file, const char *func, const int linenum)
{
LOCKINFO *info;

locklock();

info = findlock(lock, CGLOCK_UNKNOWN, file, func, linenum);


info->unlocks++;

lockunlock();
}

void api_initlock(void *lock, enum cglock_typ typ, const char *file, const char
*func, const int linenum)
{
locklock();

findlock(lock, typ, file, func, linenum);

lockunlock();
}

void dsp_det(char *msg, LOCKSTAT *stat)


{
struct tm *tm;
time_t dt;

dt = stat->tv.tv_sec;
tm = localtime(&dt);

LOCKMSGMORE("%s id=%"PRIu64" by %s %s():%d at %d-%02d-%02d %02d:%02d:%02d",


msg,
stat->lock_id,
stat->file,
stat->func,
stat->linenum,
tm->tm_year + 1900,
tm->tm_mon + 1,
tm->tm_mday,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
}
void dsp_lock(LOCKINFO *info)
{
LOCKLINE *line;
char *status;

LOCKMSG("Lock %p created by %s %s():%d",


info->lock,
info->file,
info->func,
info->linenum);
LOCKMSGMORE("gets:%"PRIu64" gots:%"PRIu64" tries:%"PRIu64
" dids:%"PRIu64" didnts:%"PRIu64" unlocks:%"PRIu64,
info->gets,
info->gots,
info->tries,
info->dids,
info->didnts,
info->unlocks);

if (info->gots > 0 || info->dids > 0) {


if (info->unlocks < info->gots + info->dids)
status = "Last got/did still HELD";
else
status = "Last got/did (idle)";

dsp_det(status, &(info->lastgot));
} else
LOCKMSGMORE("... unused ...");

if (info->lockgets) {
LOCKMSGMORE("BLOCKED gets (%"PRIu64")", info->gets - info->gots);
line = info->lockgets;
while (line) {
dsp_det("", line->stat);
line = line->next;
}
} else
LOCKMSGMORE("no blocked gets");

if (info->locktries) {
LOCKMSGMORE("BLOCKED tries (%"PRIu64")", info->tries - info->dids -
info->didnts);
line = info->lockgets;
while (line) {
dsp_det("", line->stat);
line = line->next;
}
} else
LOCKMSGMORE("no blocked tries");
}

void show_locks()
{
LOCKLIST *list;

locklock();

lockmsgnow();
list = lockhead;
if (!list)
LOCKMSG("no locks?!?\n");
else {
while (list) {
dsp_lock(list->info);
list = list->next;
}
}

LOCKMSGFLUSH();

lockunlock();
}
#endif

static void lockstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
#if LOCK_TRACKING
show_locks();
message(io_data, MSG_LOCKOK, 0, NULL, isjson);
#else
message(io_data, MSG_LOCKDIS, 0, NULL, isjson);
#endif
}

static void apiversion(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;

message(io_data, MSG_VERSION, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_VERSION : _VERSION COMSTR);

root = api_add_string(root, "CGMiner", VERSION, false);


root = api_add_const(root, "API", APIVERSION, false);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

static void minerconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;
int asccount = 0;
int pgacount = 0;

#ifdef HAVE_AN_ASIC
asccount = numascs();
#endif

#ifdef HAVE_AN_FPGA
pgacount = numpgas();
#endif

message(io_data, MSG_MINECONFIG, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_MINECONFIG : _MINECONFIG
COMSTR);

root = api_add_int(root, "ASC Count", &asccount, false);


root = api_add_int(root, "PGA Count", &pgacount, false);
root = api_add_int(root, "Pool Count", &total_pools, false);
root = api_add_const(root, "Strategy", strategies[pool_strategy].s, false);
root = api_add_int(root, "Log Interval", &opt_log_interval, false);
root = api_add_const(root, "Device Code", DEVICECODE, false);
root = api_add_const(root, "OS", OSINFO, false);
#ifdef USE_USBUTILS
if (hotplug_time == 0)
root = api_add_const(root, "Hotplug", DISABLED, false);
else
root = api_add_int(root, "Hotplug", &hotplug_time, false);
#else
root = api_add_const(root, "Hotplug", NONE, false);
#endif

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

static const char *status2str(enum alive status)


{
switch (status) {
case LIFE_WELL:
return ALIVE;
case LIFE_SICK:
return SICK;
case LIFE_DEAD:
return DEAD;
case LIFE_NOSTART:
return NOSTART;
case LIFE_INIT:
return INIT;
default:
return UNKNOWN;
}
}

#ifdef HAVE_AN_ASIC
static void ascstatus(struct io_data *io_data, int asc, bool isjson, bool precom)
{
struct api_data *root = NULL;
char *enabled;
char *status;
int numasc = numascs();

if (numasc > 0 && asc >= 0 && asc < numasc) {


int dev = ascdevice(asc);
if (dev < 0) // Should never happen
return;

struct cgpu_info *cgpu = get_devices(dev);


float temp = cgpu->temp;
double dev_runtime;

dev_runtime = cgpu_runtime(cgpu);

cgpu->utility = cgpu->accepted / dev_runtime * 60;

if (cgpu->deven != DEV_DISABLED)
enabled = (char *)YES;
else
enabled = (char *)NO;

status = (char *)status2str(cgpu->status);

root = api_add_int(root, "ASC", &asc, false);


root = api_add_string(root, "Name", cgpu->drv->name, false);
root = api_add_int(root, "ID", &(cgpu->device_id), false);
root = api_add_string(root, "Enabled", enabled, false);
root = api_add_string(root, "Status", status, false);
root = api_add_temp(root, "Temperature", &temp, false);
double mhs = cgpu->total_mhashes / dev_runtime;
root = api_add_mhs(root, "MHS av", &mhs, false);
char mhsname[27];
sprintf(mhsname, "MHS %ds", opt_log_interval);
root = api_add_mhs(root, mhsname, &(cgpu->rolling), false);
root = api_add_mhs(root, "MHS 1m", &cgpu->rolling1, false);
root = api_add_mhs(root, "MHS 5m", &cgpu->rolling5, false);
root = api_add_mhs(root, "MHS 15m", &cgpu->rolling15, false);
root = api_add_int(root, "Accepted", &(cgpu->accepted), false);
root = api_add_int(root, "Rejected", &(cgpu->rejected), false);
root = api_add_int(root, "Hardware Errors", &(cgpu->hw_errors), false);
root = api_add_utility(root, "Utility", &(cgpu->utility), false);
int last_share_pool = cgpu->last_share_pool_time > 0 ?
cgpu->last_share_pool : -1;
root = api_add_int(root, "Last Share Pool", &last_share_pool, false);
root = api_add_time(root, "Last Share Time", &(cgpu-
>last_share_pool_time), false);
root = api_add_mhtotal(root, "Total MH", &(cgpu->total_mhashes),
false);
root = api_add_int64(root, "Diff1 Work", &(cgpu->diff1), false);
root = api_add_diff(root, "Difficulty Accepted", &(cgpu-
>diff_accepted), false);
root = api_add_diff(root, "Difficulty Rejected", &(cgpu-
>diff_rejected), false);
root = api_add_diff(root, "Last Share Difficulty", &(cgpu-
>last_share_diff), false);
#ifdef USE_USBUTILS
root = api_add_bool(root, "No Device", &(cgpu->usbinfo.nodev), false);
#endif
root = api_add_time(root, "Last Valid Work", &(cgpu-
>last_device_valid_work), false);
double hwp = (cgpu->hw_errors + cgpu->diff1) ?
(double)(cgpu->hw_errors) / (double)(cgpu->hw_errors +
cgpu->diff1) : 0;
root = api_add_percent(root, "Device Hardware%", &hwp, false);
double rejp = cgpu->diff1 ?
(double)(cgpu->diff_rejected) / (double)(cgpu->diff1) : 0;
root = api_add_percent(root, "Device Rejected%", &rejp, false);
root = api_add_elapsed(root, "Device Elapsed", &(dev_runtime), false);
root = print_data(io_data, root, isjson, precom);
}
}
#endif

#ifdef HAVE_AN_FPGA
static void pgastatus(struct io_data *io_data, int pga, bool isjson, bool precom)
{
struct api_data *root = NULL;
char *enabled;
char *status;
int numpga = numpgas();

if (numpga > 0 && pga >= 0 && pga < numpga) {


int dev = pgadevice(pga);
if (dev < 0) // Should never happen
return;

struct cgpu_info *cgpu = get_devices(dev);


double frequency = 0;
float temp = cgpu->temp;
struct timeval now;
double dev_runtime;

if (cgpu->dev_start_tv.tv_sec == 0)
dev_runtime = total_secs;
else {
cgtime(&now);
dev_runtime = tdiff(&now, &(cgpu->dev_start_tv));
}

if (dev_runtime < 1.0)


dev_runtime = 1.0;

#ifdef USE_MODMINER
if (cgpu->drv->drv_id == DRIVER_modminer)
frequency = cgpu->clock;
#endif

cgpu->utility = cgpu->accepted / dev_runtime * 60;

if (cgpu->deven != DEV_DISABLED)
enabled = (char *)YES;
else
enabled = (char *)NO;

status = (char *)status2str(cgpu->status);

root = api_add_int(root, "PGA", &pga, false);


root = api_add_string(root, "Name", cgpu->drv->name, false);
root = api_add_int(root, "ID", &(cgpu->device_id), false);
root = api_add_string(root, "Enabled", enabled, false);
root = api_add_string(root, "Status", status, false);
root = api_add_temp(root, "Temperature", &temp, false);
double mhs = cgpu->total_mhashes / dev_runtime;
root = api_add_mhs(root, "MHS av", &mhs, false);
char mhsname[27];
sprintf(mhsname, "MHS %ds", opt_log_interval);
root = api_add_mhs(root, mhsname, &(cgpu->rolling), false);
root = api_add_mhs(root, "MHS 1m", &cgpu->rolling1, false);
root = api_add_mhs(root, "MHS 5m", &cgpu->rolling5, false);
root = api_add_mhs(root, "MHS 15m", &cgpu->rolling15, false);
root = api_add_int(root, "Accepted", &(cgpu->accepted), false);
root = api_add_int(root, "Rejected", &(cgpu->rejected), false);
root = api_add_int(root, "Hardware Errors", &(cgpu->hw_errors), false);
root = api_add_utility(root, "Utility", &(cgpu->utility), false);
int last_share_pool = cgpu->last_share_pool_time > 0 ?
cgpu->last_share_pool : -1;
root = api_add_int(root, "Last Share Pool", &last_share_pool, false);
root = api_add_time(root, "Last Share Time", &(cgpu-
>last_share_pool_time), false);
root = api_add_mhtotal(root, "Total MH", &(cgpu->total_mhashes),
false);
root = api_add_freq(root, "Frequency", &frequency, false);
root = api_add_int64(root, "Diff1 Work", &(cgpu->diff1), false);
root = api_add_diff(root, "Difficulty Accepted", &(cgpu-
>diff_accepted), false);
root = api_add_diff(root, "Difficulty Rejected", &(cgpu-
>diff_rejected), false);
root = api_add_diff(root, "Last Share Difficulty", &(cgpu-
>last_share_diff), false);
#ifdef USE_USBUTILS
root = api_add_bool(root, "No Device", &(cgpu->usbinfo.nodev), false);
#endif
root = api_add_time(root, "Last Valid Work", &(cgpu-
>last_device_valid_work), false);
double hwp = (cgpu->hw_errors + cgpu->diff1) ?
(double)(cgpu->hw_errors) / (double)(cgpu->hw_errors +
cgpu->diff1) : 0;
root = api_add_percent(root, "Device Hardware%", &hwp, false);
double rejp = cgpu->diff1 ?
(double)(cgpu->diff_rejected) / (double)(cgpu->diff1) : 0;
root = api_add_percent(root, "Device Rejected%", &rejp, false);
root = api_add_elapsed(root, "Device Elapsed", &(dev_runtime), false);

root = print_data(io_data, root, isjson, precom);


}
}
#endif

static void devstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
bool io_open = false;
int devcount = 0;
int numasc = 0;
int numpga = 0;
int i;

#ifdef HAVE_AN_ASIC
numasc = numascs();
#endif

#ifdef HAVE_AN_FPGA
numpga = numpgas();
#endif
if (numpga == 0 && numasc == 0) {
message(io_data, MSG_NODEVS, 0, NULL, isjson);
return;
}

message(io_data, MSG_DEVS, 0, NULL, isjson);


if (isjson)
io_open = io_add(io_data, COMSTR JSON_DEVS);

#ifdef HAVE_AN_ASIC
if (numasc > 0) {
for (i = 0; i < numasc; i++) {
ascstatus(io_data, i, isjson, isjson && devcount > 0);

devcount++;
}
}
#endif

#ifdef HAVE_AN_FPGA
if (numpga > 0) {
for (i = 0; i < numpga; i++) {
pgastatus(io_data, i, isjson, isjson && devcount > 0);

devcount++;
}
}
#endif

if (isjson && io_open)


io_close(io_data);
}

static void edevstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
bool io_open = false;
int devcount = 0;
int numasc = 0;
int numpga = 0;
int i;
#ifdef USE_USBUTILS
time_t howoldsec = 0;
#endif

#ifdef HAVE_AN_ASIC
numasc = numascs();
#endif

#ifdef HAVE_AN_FPGA
numpga = numpgas();
#endif

if (numpga == 0 && numasc == 0) {


message(io_data, MSG_NODEVS, 0, NULL, isjson);
return;
}
#ifdef USE_USBUTILS
if (param && *param)
howoldsec = (time_t)atoi(param);
#endif

message(io_data, MSG_DEVS, 0, NULL, isjson);


if (isjson)
io_open = io_add(io_data, COMSTR JSON_DEVS);

#ifdef HAVE_AN_ASIC
if (numasc > 0) {
for (i = 0; i < numasc; i++) {
#ifdef USE_USBUTILS
int dev = ascdevice(i);
if (dev < 0) // Should never happen
continue;

struct cgpu_info *cgpu = get_devices(dev);


if (!cgpu)
continue;
if (cgpu->blacklisted)
continue;
if (cgpu->usbinfo.nodev) {
if (howoldsec <= 0)
continue;
if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec)
continue;
}
#endif

ascstatus(io_data, i, isjson, isjson && devcount > 0);

devcount++;
}
}
#endif

#ifdef HAVE_AN_FPGA
if (numpga > 0) {
for (i = 0; i < numpga; i++) {
#ifdef USE_USBUTILS
int dev = pgadevice(i);
if (dev < 0) // Should never happen
continue;

struct cgpu_info *cgpu = get_devices(dev);


if (!cgpu)
continue;
if (cgpu->blacklisted)
continue;
if (cgpu->usbinfo.nodev) {
if (howoldsec <= 0)
continue;
if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec)
continue;
}
#endif

pgastatus(io_data, i, isjson, isjson && devcount > 0);


devcount++;
}
}
#endif

if (isjson && io_open)


io_close(io_data);
}

#ifdef HAVE_AN_FPGA
static void pgadev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
*param, bool isjson, __maybe_unused char group)
{
bool io_open = false;
int numpga = numpgas();
int id;

if (numpga == 0) {
message(io_data, MSG_PGANON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numpga) {
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

message(io_data, MSG_PGADEV, id, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_PGA);

pgastatus(io_data, id, isjson, false);

if (isjson && io_open)


io_close(io_data);
}

static void pgaenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
int numpga = numpgas();
struct thr_info *thr;
int pga;
int id;
int i;

if (numpga == 0) {
message(io_data, MSG_PGANON, 0, NULL, isjson);
return;
}
if (param == NULL || *param == '\0') {
message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numpga) {
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

int dev = pgadevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);

applog(LOG_DEBUG, "API: request to pgaenable pgaid %d device %d %s%u",


id, dev, cgpu->drv->name, cgpu->device_id);

if (cgpu->deven != DEV_DISABLED) {
message(io_data, MSG_PGALRENA, id, NULL, isjson);
return;
}

#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL?
*/
if (cgpu->status != LIFE_WELL) {
message(io_data, MSG_PGAUNW, id, NULL, isjson);
return;
}
#endif

#ifdef USE_USBUTILS
if (cgpu->usbinfo.nodev) {
message(io_data, MSG_PGAUSBNODEV, id, NULL, isjson);
return;
}
#endif

for (i = 0; i < mining_threads; i++) {


thr = get_thread(i);
pga = thr->cgpu->cgminer_id;
if (pga == dev) {
cgpu->deven = DEV_ENABLED;
applog(LOG_DEBUG, "API: Pushing sem post to thread %d", thr->id);
cgsem_post(&thr->sem);
}
}

message(io_data, MSG_PGAENA, id, NULL, isjson);


}

static void pgadisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
int numpga = numpgas();
int id;

if (numpga == 0) {
message(io_data, MSG_PGANON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numpga) {
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

int dev = pgadevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);

applog(LOG_DEBUG, "API: request to pgadisable pgaid %d device %d %s%u",


id, dev, cgpu->drv->name, cgpu->device_id);

if (cgpu->deven == DEV_DISABLED) {
message(io_data, MSG_PGALRDIS, id, NULL, isjson);
return;
}

cgpu->deven = DEV_DISABLED;

message(io_data, MSG_PGADIS, id, NULL, isjson);


}

static void pgaidentify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
struct device_drv *drv;
int numpga = numpgas();
int id;

if (numpga == 0) {
message(io_data, MSG_PGANON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}
id = atoi(param);
if (id < 0 || id >= numpga) {
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

int dev = pgadevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);
drv = cgpu->drv;

if (!drv->identify_device)
message(io_data, MSG_PGANOID, id, NULL, isjson);
else {
drv->identify_device(cgpu);
message(io_data, MSG_PGAIDENT, id, NULL, isjson);
}
}
#endif

static void poolstatus(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open = false;
char *status, *lp;
int i;
double sdiff0 = 0.0;

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}

message(io_data, MSG_POOL, 0, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_POOLS);

for (i = 0; i < total_pools; i++) {


struct pool *pool = pools[i];

if (pool->removed)
continue;

switch (pool->enabled) {
case POOL_DISABLED:
status = (char *)DISABLED;
break;
case POOL_REJECTING:
status = (char *)REJECTING;
break;
case POOL_ENABLED:
if (pool->idle)
status = (char *)DEAD;
else
status = (char *)ALIVE;
break;
default:
status = (char *)UNKNOWN;
break;
}

if (pool->hdr_path)
lp = (char *)YES;
else
lp = (char *)NO;

root = api_add_int(root, "POOL", &i, false);


root = api_add_escape(root, "URL", pool->rpc_url, false);
root = api_add_string(root, "Status", status, false);
root = api_add_int(root, "Priority", &(pool->prio), false);
root = api_add_int(root, "Quota", &pool->quota, false);
root = api_add_string(root, "Long Poll", lp, false);
root = api_add_uint(root, "Getworks", &(pool->getwork_requested),
false);
root = api_add_int64(root, "Accepted", &(pool->accepted), false);
root = api_add_int64(root, "Rejected", &(pool->rejected), false);
root = api_add_int(root, "Works", &pool->works, false);
root = api_add_uint(root, "Discarded", &(pool->discarded_work), false);
root = api_add_uint(root, "Stale", &(pool->stale_shares), false);
root = api_add_uint(root, "Get Failures", &(pool->getfail_occasions),
false);
root = api_add_uint(root, "Remote Failures", &(pool-
>remotefail_occasions), false);
root = api_add_escape(root, "User", pool->rpc_user, false);
root = api_add_time(root, "Last Share Time", &(pool->last_share_time),
false);
root = api_add_int64(root, "Diff1 Shares", &(pool->diff1), false);
if (pool->rpc_proxy) {
root = api_add_const(root, "Proxy Type", proxytype(pool-
>rpc_proxytype), false);
root = api_add_escape(root, "Proxy", pool->rpc_proxy, false);
} else {
root = api_add_const(root, "Proxy Type", BLANK, false);
root = api_add_const(root, "Proxy", BLANK, false);
}
root = api_add_diff(root, "Difficulty Accepted", &(pool-
>diff_accepted), false);
root = api_add_diff(root, "Difficulty Rejected", &(pool-
>diff_rejected), false);
root = api_add_diff(root, "Difficulty Stale", &(pool->diff_stale),
false);
root = api_add_diff(root, "Last Share Difficulty", &(pool-
>last_share_diff), false);
root = api_add_diff(root, "Work Difficulty", &(pool-
>cgminer_pool_stats.last_diff), false);
root = api_add_bool(root, "Has Stratum", &(pool->has_stratum), false);
root = api_add_bool(root, "Stratum Active", &(pool->stratum_active),
false);
if (pool->stratum_active) {
root = api_add_escape(root, "Stratum URL", pool->stratum_url,
false);
root = api_add_diff(root, "Stratum Difficulty", &(pool->sdiff),
false);
} else {
root = api_add_const(root, "Stratum URL", BLANK, false);
root = api_add_diff(root, "Stratum Difficulty", &(sdiff0),
false);
}
root = api_add_bool(root, "Has Vmask", &(pool->vmask), false);
root = api_add_bool(root, "Has GBT", &(pool->has_gbt), false);
root = api_add_uint64(root, "Best Share", &(pool->best_diff), true);
double rejp = (pool->diff_accepted + pool->diff_rejected + pool-
>diff_stale) ?
(double)(pool->diff_rejected) / (double)(pool-
>diff_accepted + pool->diff_rejected + pool->diff_stale) : 0;
root = api_add_percent(root, "Pool Rejected%", &rejp, false);
double stalep = (pool->diff_accepted + pool->diff_rejected + pool-
>diff_stale) ?
(double)(pool->diff_stale) / (double)(pool->diff_accepted +
pool->diff_rejected + pool->diff_stale) : 0;
root = api_add_percent(root, "Pool Stale%", &stalep, false);
root = api_add_uint64(root, "Bad Work", &(pool->bad_work), true);
root = api_add_uint32(root, "Current Block Height", &(pool-
>current_height), true);
uint32_t nversion = (uint32_t)strtoul(pool->bbversion, NULL, 16);
root = api_add_uint32(root, "Current Block Version", &nversion, true);

root = print_data(io_data, root, isjson, isjson && (i > 0));


}

if (isjson && io_open)


io_close(io_data);
}

static void summary(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;
double utility, mhs, work_utility;

message(io_data, MSG_SUMM, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_SUMMARY : _SUMMARY COMSTR);

// stop hashmeter() changing some while copying


mutex_lock(&hash_lock);

utility = total_accepted / ( total_secs ? total_secs : 1 ) * 60;


mhs = total_mhashes_done / total_secs;
work_utility = total_diff1 / ( total_secs ? total_secs : 1 ) * 60;

root = api_add_elapsed(root, "Elapsed", &(total_secs), true);


root = api_add_mhs(root, "MHS av", &(mhs), false);
char mhsname[27];
sprintf(mhsname, "MHS %ds", opt_log_interval);
root = api_add_mhs(root, mhsname, &(total_rolling), false);
root = api_add_mhs(root, "MHS 1m", &rolling1, false);
root = api_add_mhs(root, "MHS 5m", &rolling5, false);
root = api_add_mhs(root, "MHS 15m", &rolling15, false);
root = api_add_uint(root, "Found Blocks", &(found_blocks), true);
root = api_add_int64(root, "Getworks", &(total_getworks), true);
root = api_add_int64(root, "Accepted", &(total_accepted), true);
root = api_add_int64(root, "Rejected", &(total_rejected), true);
root = api_add_int(root, "Hardware Errors", &(hw_errors), true);
root = api_add_utility(root, "Utility", &(utility), false);
root = api_add_int64(root, "Discarded", &(total_discarded), true);
root = api_add_int64(root, "Stale", &(total_stale), true);
root = api_add_uint(root, "Get Failures", &(total_go), true);
root = api_add_uint(root, "Local Work", &(local_work), true);
root = api_add_uint(root, "Remote Failures", &(total_ro), true);
root = api_add_uint(root, "Network Blocks", &(new_blocks), true);
root = api_add_mhtotal(root, "Total MH", &(total_mhashes_done), true);
root = api_add_utility(root, "Work Utility", &(work_utility), false);
root = api_add_diff(root, "Difficulty Accepted", &(total_diff_accepted),
true);
root = api_add_diff(root, "Difficulty Rejected", &(total_diff_rejected),
true);
root = api_add_diff(root, "Difficulty Stale", &(total_diff_stale), true);
root = api_add_uint64(root, "Best Share", &(best_diff), true);
double hwp = (hw_errors + total_diff1) ?
(double)(hw_errors) / (double)(hw_errors + total_diff1) : 0;
root = api_add_percent(root, "Device Hardware%", &hwp, false);
double rejp = total_diff1 ?
(double)(total_diff_rejected) / (double)(total_diff1) : 0;
root = api_add_percent(root, "Device Rejected%", &rejp, false);
double prejp = (total_diff_accepted + total_diff_rejected + total_diff_stale)
?
(double)(total_diff_rejected) / (double)(total_diff_accepted +
total_diff_rejected + total_diff_stale) : 0;
root = api_add_percent(root, "Pool Rejected%", &prejp, false);
double stalep = (total_diff_accepted + total_diff_rejected +
total_diff_stale) ?
(double)(total_diff_stale) / (double)(total_diff_accepted +
total_diff_rejected + total_diff_stale) : 0;
root = api_add_percent(root, "Pool Stale%", &stalep, false);
root = api_add_time(root, "Last getwork", &last_getwork, false);

mutex_unlock(&hash_lock);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

static void pgacount(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;
int count = 0;

#ifdef HAVE_AN_FPGA
count = numpgas();
#endif

message(io_data, MSG_NUMPGA, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_PGAS : _PGAS COMSTR);

root = api_add_int(root, "Count", &count, false);


root = print_data(io_data, root, isjson, false);
if (isjson && io_open)
io_close(io_data);
}

static void switchpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct pool *pool;
int id;

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISPID, 0, NULL, isjson);
return;
}

id = atoi(param);
cg_rlock(&control_lock);
if (id < 0 || id >= total_pools) {
cg_runlock(&control_lock);
message(io_data, MSG_INVPID, id, NULL, isjson);
return;
}

pool = pools[id];
pool->enabled = POOL_ENABLED;
cg_runlock(&control_lock);
switch_pools(pool);

message(io_data, MSG_SWITCHP, id, NULL, isjson);


}

static void copyadvanceafter(char ch, char **param, char **buf)


{
#define src_p (*param)
#define dst_b (*buf)

while (*src_p && *src_p != ch) {


if (*src_p == '\\' && *(src_p+1) != '\0')
src_p++;

*(dst_b++) = *(src_p++);
}
if (*src_p)
src_p++;

*(dst_b++) = '\0';
}

static bool pooldetails(char *param, char **url, char **user, char **pass)
{
char *ptr, *buf;

ptr = buf = cgmalloc(strlen(param)+1);


*url = buf;

// copy url
copyadvanceafter(',', &param, &buf);

if (!(*param)) // missing user


goto exitsama;

*user = buf;

// copy user
copyadvanceafter(',', &param, &buf);

if (!*param) // missing pass


goto exitsama;

*pass = buf;

// copy pass
copyadvanceafter(',', &param, &buf);

return true;

exitsama:
free(ptr);
return false;
}

static void addpool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
char *url, *user, *pass;
struct pool *pool;
char *ptr;

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISPDP, 0, NULL, isjson);
return;
}

if (!pooldetails(param, &url, &user, &pass)) {


ptr = escape_string(param, isjson);
message(io_data, MSG_INVPDP, 0, ptr, isjson);
if (ptr != param)
free(ptr);
ptr = NULL;
return;
}

pool = add_pool();
detect_stratum(pool, url);
add_pool_details(pool, true, url, user, pass);

ptr = escape_string(url, isjson);


message(io_data, MSG_ADDPOOL, pool->pool_no, ptr, isjson);
if (ptr != url)
free(ptr);
ptr = NULL;
}

static void enablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct pool *pool;
int id;

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISPID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= total_pools) {
message(io_data, MSG_INVPID, id, NULL, isjson);
return;
}

pool = pools[id];
if (pool->enabled == POOL_ENABLED) {
message(io_data, MSG_ALRENAP, id, NULL, isjson);
return;
}

pool->enabled = POOL_ENABLED;
if (pool->prio < current_pool()->prio)
switch_pools(pool);

message(io_data, MSG_ENAPOOL, id, NULL, isjson);


}

static void poolpriority(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
char *ptr, *next;
int i, pr, prio = 0;

// TODO: all cgminer code needs a mutex added everywhere for change
// access to total_pools and also parts of the pools[] array,
// just copying total_pools here wont solve that

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISPID, 0, NULL, isjson);
return;
}

bool pools_changed[total_pools];
int new_prio[total_pools];
for (i = 0; i < total_pools; ++i)
pools_changed[i] = false;

next = param;
while (next && *next) {
ptr = next;
next = strchr(ptr, ',');
if (next)
*(next++) = '\0';

i = atoi(ptr);
if (i < 0 || i >= total_pools) {
message(io_data, MSG_INVPID, i, NULL, isjson);
return;
}

if (pools_changed[i]) {
message(io_data, MSG_DUPPID, i, NULL, isjson);
return;
}

pools_changed[i] = true;
new_prio[i] = prio++;
}

// Only change them if no errors


for (i = 0; i < total_pools; i++) {
if (pools_changed[i])
pools[i]->prio = new_prio[i];
}

// In priority order, cycle through the unchanged pools and append them
for (pr = 0; pr < total_pools; pr++)
for (i = 0; i < total_pools; i++) {
if (!pools_changed[i] && pools[i]->prio == pr) {
pools[i]->prio = prio++;
pools_changed[i] = true;
break;
}
}

if (current_pool()->prio)
switch_pools(NULL);

message(io_data, MSG_POOLPRIO, 0, NULL, isjson);


}

static void poolquota(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct pool *pool;
int quota, id;
char *comma;

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}
if (param == NULL || *param == '\0') {
message(io_data, MSG_MISPID, 0, NULL, isjson);
return;
}

comma = strchr(param, ',');


if (!comma) {
message(io_data, MSG_CONVAL, 0, param, isjson);
return;
}

*(comma++) = '\0';

id = atoi(param);
if (id < 0 || id >= total_pools) {
message(io_data, MSG_INVPID, id, NULL, isjson);
return;
}
pool = pools[id];

quota = atoi(comma);
if (quota < 0) {
message(io_data, MSG_INVNEG, quota, pool->rpc_url, isjson);
return;
}

pool->quota = quota;
adjust_quota_gcd();
message(io_data, MSG_SETQUOTA, quota, pool->rpc_url, isjson);
}

static void disablepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct pool *pool;
int id;

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISPID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= total_pools) {
message(io_data, MSG_INVPID, id, NULL, isjson);
return;
}

pool = pools[id];
if (pool->enabled == POOL_DISABLED) {
message(io_data, MSG_ALRDISP, id, NULL, isjson);
return;
}
if (enabled_pools <= 1) {
message(io_data, MSG_DISLASTP, id, NULL, isjson);
return;
}

pool->enabled = POOL_DISABLED;
if (pool == current_pool())
switch_pools(NULL);

message(io_data, MSG_DISPOOL, id, NULL, isjson);


}

static void removepool(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct pool *pool;
char *rpc_url;
bool dofree = false;
int id;

if (total_pools == 0) {
message(io_data, MSG_NOPOOL, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISPID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= total_pools) {
message(io_data, MSG_INVPID, id, NULL, isjson);
return;
}

if (total_pools <= 1) {
message(io_data, MSG_REMLASTP, id, NULL, isjson);
return;
}

pool = pools[id];
if (pool == current_pool())
switch_pools(NULL);

if (pool == current_pool()) {
message(io_data, MSG_ACTPOOL, id, NULL, isjson);
return;
}

pool->enabled = POOL_DISABLED;
rpc_url = escape_string(pool->rpc_url, isjson);
if (rpc_url != pool->rpc_url)
dofree = true;

remove_pool(pool);

message(io_data, MSG_REMPOOL, id, rpc_url, isjson);


if (dofree)
free(rpc_url);
rpc_url = NULL;
}

void doquit(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused


char *param, bool isjson, __maybe_unused char group)
{
if (isjson)
io_put(io_data, JSON_ACTION JSON_BYE);
else
io_put(io_data, _BYE);

bye = true;
do_a_quit = true;
}

void dorestart(struct io_data *io_data, __maybe_unused SOCKETTYPE c, __maybe_unused


char *param, bool isjson, __maybe_unused char group)
{
if (isjson)
io_put(io_data, JSON_ACTION JSON_RESTART);
else
io_put(io_data, _RESTART);

bye = true;
do_a_restart = true;
}

void privileged(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
message(io_data, MSG_ACCOK, 0, NULL, isjson);
}

void notifystatus(struct io_data *io_data, int device, struct cgpu_info *cgpu, bool
isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
char *reason;

if (cgpu->device_last_not_well == 0)
reason = REASON_NONE;
else
switch(cgpu->device_not_well_reason) {
case REASON_THREAD_FAIL_INIT:
reason = REASON_THREAD_FAIL_INIT_STR;
break;
case REASON_THREAD_ZERO_HASH:
reason = REASON_THREAD_ZERO_HASH_STR;
break;
case REASON_THREAD_FAIL_QUEUE:
reason = REASON_THREAD_FAIL_QUEUE_STR;
break;
case REASON_DEV_SICK_IDLE_60:
reason = REASON_DEV_SICK_IDLE_60_STR;
break;
case REASON_DEV_DEAD_IDLE_600:
reason = REASON_DEV_DEAD_IDLE_600_STR;
break;
case REASON_DEV_NOSTART:
reason = REASON_DEV_NOSTART_STR;
break;
case REASON_DEV_OVER_HEAT:
reason = REASON_DEV_OVER_HEAT_STR;
break;
case REASON_DEV_THERMAL_CUTOFF:
reason = REASON_DEV_THERMAL_CUTOFF_STR;
break;
case REASON_DEV_COMMS_ERROR:
reason = REASON_DEV_COMMS_ERROR_STR;
break;
default:
reason = REASON_UNKNOWN_STR;
break;
}

// ALL counters (and only counters) must start the name with a '*'
// Simplifies future external support for identifying new counters
root = api_add_int(root, "NOTIFY", &device, false);
root = api_add_string(root, "Name", cgpu->drv->name, false);
root = api_add_int(root, "ID", &(cgpu->device_id), false);
root = api_add_time(root, "Last Well", &(cgpu->device_last_well), false);
root = api_add_time(root, "Last Not Well", &(cgpu->device_last_not_well),
false);
root = api_add_string(root, "Reason Not Well", reason, false);
root = api_add_int(root, "*Thread Fail Init", &(cgpu-
>thread_fail_init_count), false);
root = api_add_int(root, "*Thread Zero Hash", &(cgpu-
>thread_zero_hash_count), false);
root = api_add_int(root, "*Thread Fail Queue", &(cgpu-
>thread_fail_queue_count), false);
root = api_add_int(root, "*Dev Sick Idle 60s", &(cgpu-
>dev_sick_idle_60_count), false);
root = api_add_int(root, "*Dev Dead Idle 600s", &(cgpu-
>dev_dead_idle_600_count), false);
root = api_add_int(root, "*Dev Nostart", &(cgpu->dev_nostart_count), false);
root = api_add_int(root, "*Dev Over Heat", &(cgpu->dev_over_heat_count),
false);
root = api_add_int(root, "*Dev Thermal Cutoff", &(cgpu-
>dev_thermal_cutoff_count), false);
root = api_add_int(root, "*Dev Comms Error", &(cgpu->dev_comms_error_count),
false);
root = api_add_int(root, "*Dev Throttle", &(cgpu->dev_throttle_count),
false);

root = print_data(io_data, root, isjson, isjson && (device > 0));


}

static void notify(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, char group)
{
struct cgpu_info *cgpu;
bool io_open = false;
int i;

if (total_devices == 0) {
message(io_data, MSG_NODEVS, 0, NULL, isjson);
return;
}

message(io_data, MSG_NOTIFY, 0, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_NOTIFY);

for (i = 0; i < total_devices; i++) {


cgpu = get_devices(i);
notifystatus(io_data, i, cgpu, isjson, group);
}

if (isjson && io_open)


io_close(io_data);
}

static void devdetails(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open = false;
struct cgpu_info *cgpu;
int i;

if (total_devices == 0) {
message(io_data, MSG_NODEVS, 0, NULL, isjson);
return;
}

message(io_data, MSG_DEVDETAILS, 0, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_DEVDETAILS);

for (i = 0; i < total_devices; i++) {


cgpu = get_devices(i);

root = api_add_int(root, "DEVDETAILS", &i, false);


root = api_add_string(root, "Name", cgpu->drv->name, false);
root = api_add_int(root, "ID", &(cgpu->device_id), false);
root = api_add_string(root, "Driver", cgpu->drv->dname, false);
root = api_add_const(root, "Kernel", cgpu->kname ? : BLANK, false);
root = api_add_const(root, "Model", cgpu->name ? : BLANK, false);
root = api_add_const(root, "Device Path", cgpu->device_path ? : BLANK,
false);

root = print_data(io_data, root, isjson, isjson && (i > 0));


}

if (isjson && io_open)


io_close(io_data);
}

void dosave(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char *param, bool


isjson, __maybe_unused char group)
{
char filename[PATH_MAX];
FILE *fcfg;
char *ptr;

if (param == NULL || *param == '\0') {


default_save_file(filename);
param = filename;
}

fcfg = fopen(param, "w");


if (!fcfg) {
ptr = escape_string(param, isjson);
message(io_data, MSG_BADFN, 0, ptr, isjson);
if (ptr != param)
free(ptr);
ptr = NULL;
return;
}

write_config(fcfg);
fclose(fcfg);

ptr = escape_string(param, isjson);


message(io_data, MSG_SAVED, 0, ptr, isjson);
if (ptr != param)
free(ptr);
ptr = NULL;
}

static int itemstats(struct io_data *io_data, int i, char *id, struct cgminer_stats
*stats, struct cgminer_pool_stats *pool_stats, struct api_data *extra, struct
cgpu_info *cgpu, bool isjson)
{
struct api_data *root = NULL;

root = api_add_int(root, "STATS", &i, false);


root = api_add_string(root, "ID", id, false);
root = api_add_elapsed(root, "Elapsed", &(total_secs), false);
root = api_add_uint32(root, "Calls", &(stats->getwork_calls), false);
root = api_add_timeval(root, "Wait", &(stats->getwork_wait), false);
root = api_add_timeval(root, "Max", &(stats->getwork_wait_max), false);
root = api_add_timeval(root, "Min", &(stats->getwork_wait_min), false);

if (pool_stats) {
root = api_add_uint32(root, "Pool Calls", &(pool_stats->getwork_calls),
false);
root = api_add_uint32(root, "Pool Attempts", &(pool_stats-
>getwork_attempts), false);
root = api_add_timeval(root, "Pool Wait", &(pool_stats->getwork_wait),
false);
root = api_add_timeval(root, "Pool Max", &(pool_stats-
>getwork_wait_max), false);
root = api_add_timeval(root, "Pool Min", &(pool_stats-
>getwork_wait_min), false);
root = api_add_double(root, "Pool Av", &(pool_stats-
>getwork_wait_rolling), false);
root = api_add_bool(root, "Work Had Roll Time", &(pool_stats-
>hadrolltime), false);
root = api_add_bool(root, "Work Can Roll", &(pool_stats->canroll),
false);
root = api_add_bool(root, "Work Had Expire", &(pool_stats->hadexpire),
false);
root = api_add_uint32(root, "Work Roll Time", &(pool_stats->rolltime),
false);
root = api_add_diff(root, "Work Diff", &(pool_stats->last_diff),
false);
root = api_add_diff(root, "Min Diff", &(pool_stats->min_diff), false);
root = api_add_diff(root, "Max Diff", &(pool_stats->max_diff), false);
root = api_add_uint32(root, "Min Diff Count", &(pool_stats-
>min_diff_count), false);
root = api_add_uint32(root, "Max Diff Count", &(pool_stats-
>max_diff_count), false);
root = api_add_uint64(root, "Times Sent", &(pool_stats->times_sent),
false);
root = api_add_uint64(root, "Bytes Sent", &(pool_stats->bytes_sent),
false);
root = api_add_uint64(root, "Times Recv", &(pool_stats-
>times_received), false);
root = api_add_uint64(root, "Bytes Recv", &(pool_stats-
>bytes_received), false);
root = api_add_uint64(root, "Net Bytes Sent", &(pool_stats-
>net_bytes_sent), false);
root = api_add_uint64(root, "Net Bytes Recv", &(pool_stats-
>net_bytes_received), false);
}

if (extra)
root = api_add_extra(root, extra);

if (cgpu) {
#ifdef USE_USBUTILS
char details[256];

if (cgpu->usbinfo.pipe_count)
snprintf(details, sizeof(details),
"%"PRIu64" %"PRIu64"/%"PRIu64"/%"PRIu64" %lu",
cgpu->usbinfo.pipe_count,
cgpu->usbinfo.clear_err_count,
cgpu->usbinfo.retry_err_count,
cgpu->usbinfo.clear_fail_count,
(unsigned long)(cgpu->usbinfo.last_pipe));
else
strcpy(details, "0");

root = api_add_string(root, "USB Pipe", details, true);

snprintf(details, sizeof(details),
"r%"PRIu64" %.6f w%"PRIu64" %.6f",
cgpu->usbinfo.read_delay_count,
cgpu->usbinfo.total_read_delay,
cgpu->usbinfo.write_delay_count,
cgpu->usbinfo.total_write_delay);

root = api_add_string(root, "USB Delay", details, true);

if (cgpu->usbinfo.usb_tmo[0].count == 0 &&
cgpu->usbinfo.usb_tmo[1].count == 0 &&
cgpu->usbinfo.usb_tmo[2].count == 0) {
snprintf(details, sizeof(details),
"%"PRIu64" 0", cgpu->usbinfo.tmo_count);
} else {
snprintf(details, sizeof(details),
"%"PRIu64" %d=%d/%d/%d/%"PRIu64"/%"PRIu64
" %d=%d/%d/%d/%"PRIu64"/%"PRIu64
" %d=%d/%d/%d/%"PRIu64"/%"PRIu64" ",
cgpu->usbinfo.tmo_count,
USB_TMO_0, cgpu->usbinfo.usb_tmo[0].count,
cgpu->usbinfo.usb_tmo[0].min_tmo,
cgpu->usbinfo.usb_tmo[0].max_tmo,
cgpu->usbinfo.usb_tmo[0].total_over,
cgpu->usbinfo.usb_tmo[0].total_tmo,
USB_TMO_1, cgpu->usbinfo.usb_tmo[1].count,
cgpu->usbinfo.usb_tmo[1].min_tmo,
cgpu->usbinfo.usb_tmo[1].max_tmo,
cgpu->usbinfo.usb_tmo[1].total_over,
cgpu->usbinfo.usb_tmo[1].total_tmo,
USB_TMO_2, cgpu->usbinfo.usb_tmo[2].count,
cgpu->usbinfo.usb_tmo[2].min_tmo,
cgpu->usbinfo.usb_tmo[2].max_tmo,
cgpu->usbinfo.usb_tmo[2].total_over,
cgpu->usbinfo.usb_tmo[2].total_tmo);
}

root = api_add_string(root, "USB tmo", details, true);


#endif
}

root = print_data(io_data, root, isjson, isjson && (i > 0));

return ++i;
}

static void minerstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
bool io_open = false;
struct api_data *extra;
char id[20];
int i, j;

message(io_data, MSG_MINESTATS, 0, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_MINESTATS);

i = 0;
for (j = 0; j < total_devices; j++) {
cgpu = get_devices(j);

if (cgpu && cgpu->drv) {


if (cgpu->drv->get_api_stats)
extra = cgpu->drv->get_api_stats(cgpu);
else
extra = NULL;

sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id);


i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL,
extra, cgpu, isjson);
}
}

for (j = 0; j < total_pools; j++) {


struct pool *pool = pools[j];

sprintf(id, "POOL%d", j);


i = itemstats(io_data, i, id, &(pool->cgminer_stats), &(pool-
>cgminer_pool_stats), NULL, NULL, isjson);
}

if (isjson && io_open)


io_close(io_data);
}

static void minerdebug(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
bool io_open = false;
struct api_data *extra;
char id[20];
int i, j;

message(io_data, MSG_MINEDEBUG, 0, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_MINESTATS);

i = 0;
for (j = 0; j < total_devices; j++) {
cgpu = get_devices(j);

if (cgpu && cgpu->drv) {


if (cgpu->drv->get_api_debug)
extra = cgpu->drv->get_api_debug(cgpu);
else
extra = NULL;

sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id);


i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL,
extra, cgpu, isjson);
}
}

for (j = 0; j < total_pools; j++) {


struct pool *pool = pools[j];

sprintf(id, "POOL%d", j);


i = itemstats(io_data, i, id, &(pool->cgminer_stats), &(pool-
>cgminer_pool_stats), NULL, NULL, isjson);
}

if (isjson && io_open)


io_close(io_data);
}

static void minerestats(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
bool io_open = false;
struct api_data *extra;
char id[20];
int i, j;
#ifdef USE_USBUTILS
time_t howoldsec = 0;

if (param && *param)


howoldsec = (time_t)atoi(param);
#endif

message(io_data, MSG_MINESTATS, 0, NULL, isjson);


if (isjson)
io_open = io_add(io_data, COMSTR JSON_MINESTATS);

i = 0;
for (j = 0; j < total_devices; j++) {
cgpu = get_devices(j);
if (!cgpu)
continue;
#ifdef USE_USBUTILS
if (cgpu->blacklisted)
continue;
if (cgpu->usbinfo.nodev) {
if (howoldsec <= 0)
continue;
if ((when - cgpu->usbinfo.last_nodev.tv_sec) >= howoldsec)
continue;
}
#endif
if (cgpu->drv) {
if (cgpu->drv->get_api_stats)
extra = cgpu->drv->get_api_stats(cgpu);
else
extra = NULL;

sprintf(id, "%s%d", cgpu->drv->name, cgpu->device_id);


i = itemstats(io_data, i, id, &(cgpu->cgminer_stats), NULL,
extra, cgpu, isjson);
}
}

if (isjson && io_open)


io_close(io_data);
}

static void failoveronly(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
message(io_data, MSG_DEPRECATED, 0, param, isjson);
}

static void minecoin(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;
message(io_data, MSG_MINECOIN, 0, NULL, isjson);
io_open = io_add(io_data, isjson ? COMSTR JSON_MINECOIN : _MINECOIN COMSTR);

root = api_add_const(root, "Hash Method", SHA256STR, false);

cg_rlock(&ch_lock);
root = api_add_timeval(root, "Current Block Time", &block_timeval, true);
root = api_add_string(root, "Current Block Hash", current_hash, true);
cg_runlock(&ch_lock);

root = api_add_bool(root, "LP", &have_longpoll, false);


root = api_add_diff(root, "Network Difficulty", &current_diff, true);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

static void debugstate(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;

if (param == NULL)
param = (char *)BLANK;
else
*param = tolower(*param);

switch(*param) {
case 's':
opt_realquiet = true;
break;
case 'q':
opt_quiet ^= true;
break;
case 'v':
opt_log_output ^= true;
if (opt_log_output)
opt_quiet = false;
break;
case 'd':
opt_debug ^= true;
opt_log_output = opt_debug;
if (opt_debug)
opt_quiet = false;
break;
case 'r':
opt_protocol ^= true;
if (opt_protocol)
opt_quiet = false;
break;
case 'p':
want_per_device_stats ^= true;
opt_log_output = want_per_device_stats;
break;
case 'n':
opt_log_output = false;
opt_debug = false;
opt_quiet = false;
opt_protocol = false;
want_per_device_stats = false;
opt_worktime = false;
break;
case 'w':
opt_worktime ^= true;
break;
#ifdef _MEMORY_DEBUG
case 'y':
cgmemspeedup();
break;
case 'z':
cgmemrpt();
break;
#endif
default:
// anything else just reports the settings
break;
}

message(io_data, MSG_DEBUGSET, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_DEBUGSET : _DEBUGSET COMSTR);

root = api_add_bool(root, "Silent", &opt_realquiet, false);


root = api_add_bool(root, "Quiet", &opt_quiet, false);
root = api_add_bool(root, "Verbose", &opt_log_output, false);
root = api_add_bool(root, "Debug", &opt_debug, false);
root = api_add_bool(root, "RPCProto", &opt_protocol, false);
root = api_add_bool(root, "PerDevice", &want_per_device_stats, false);
root = api_add_bool(root, "WorkTime", &opt_worktime, false);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

static void setconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
if (!strcasecmp(param, "queue") || ! strcasecmp(param, "scantime") || !
strcasecmp(param, "expiry"))
message(io_data, MSG_DEPRECATED, 0, param, isjson);

message(io_data, MSG_UNKCON, 0, param, isjson);


}

static void usbstats(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;

#ifdef USE_USBUTILS
bool io_open = false;
int count = 0;

root = api_usb_stats(&count);
#endif
if (!root) {
message(io_data, MSG_NOUSTA, 0, NULL, isjson);
return;
}

#ifdef USE_USBUTILS
message(io_data, MSG_USBSTA, 0, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_USBSTATS);

root = print_data(io_data, root, isjson, false);

while (42) {
root = api_usb_stats(&count);
if (!root)
break;

root = print_data(io_data, root, isjson, isjson);


}

if (isjson && io_open)


io_close(io_data);
#endif
}

#ifdef HAVE_AN_FPGA
static void pgaset(struct io_data *io_data, __maybe_unused SOCKETTYPE c,
__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
struct device_drv *drv;
char buf[TMPBUFSIZ];
int numpga = numpgas();

if (numpga == 0) {
message(io_data, MSG_PGANON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

char *opt = strchr(param, ',');


if (opt)
*(opt++) = '\0';
if (!opt || !*opt) {
message(io_data, MSG_MISPGAOPT, 0, NULL, isjson);
return;
}

int id = atoi(param);
if (id < 0 || id >= numpga) {
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}
int dev = pgadevice(id);
if (dev < 0) { // Should never happen
message(io_data, MSG_INVPGA, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);
drv = cgpu->drv;

char *set = strchr(opt, ',');


if (set)
*(set++) = '\0';

if (!drv->set_device)
message(io_data, MSG_PGANOSET, id, NULL, isjson);
else {
char *ret = drv->set_device(cgpu, opt, set, buf);
if (ret) {
if (strcasecmp(opt, "help") == 0)
message(io_data, MSG_PGAHELP, id, ret, isjson);
else
message(io_data, MSG_PGASETERR, id, ret, isjson);
} else
message(io_data, MSG_PGASETOK, id, NULL, isjson);
}
}
#endif

static void dozero(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
if (param == NULL || *param == '\0') {
message(io_data, MSG_ZERMIS, 0, NULL, isjson);
return;
}

char *sum = strchr(param, ',');


if (sum)
*(sum++) = '\0';
if (!sum || !*sum) {
message(io_data, MSG_MISBOOL, 0, NULL, isjson);
return;
}

bool all = false;


bool bs = false;
if (strcasecmp(param, "all") == 0)
all = true;
else if (strcasecmp(param, "bestshare") == 0)
bs = true;

if (all == false && bs == false) {


message(io_data, MSG_ZERINV, 0, param, isjson);
return;
}

*sum = tolower(*sum);
if (*sum != 't' && *sum != 'f') {
message(io_data, MSG_INVBOOL, 0, NULL, isjson);
return;
}

bool dosum = (*sum == 't');


if (dosum)
print_summary();

if (all)
zero_stats();
if (bs)
zero_bestshare();

if (dosum)
message(io_data, MSG_ZERSUM, 0, all ? "All" : "BestShare", isjson);
else
message(io_data, MSG_ZERNOSUM, 0, all ? "All" : "BestShare", isjson);
}

static void dohotplug(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
#ifdef USE_USBUTILS
int value;

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISHPLG, 0, NULL, isjson);
return;
}

value = atoi(param);
if (value < 0 || value > 9999) {
message(io_data, MSG_INVHPLG, 0, param, isjson);
return;
}

hotplug_time = value;

if (value)
message(io_data, MSG_HOTPLUG, value, NULL, isjson);
else
message(io_data, MSG_DISHPLG, 0, NULL, isjson);
#else
message(io_data, MSG_NOHPLG, 0, NULL, isjson);
return;
#endif
}

#ifdef HAVE_AN_ASIC
static void ascdev(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
*param, bool isjson, __maybe_unused char group)
{
bool io_open = false;
int numasc = numascs();
int id;

if (numasc == 0) {
message(io_data, MSG_ASCNON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numasc) {
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

message(io_data, MSG_ASCDEV, id, NULL, isjson);

if (isjson)
io_open = io_add(io_data, COMSTR JSON_ASC);

ascstatus(io_data, id, isjson, false);

if (isjson && io_open)


io_close(io_data);
}

static void ascenable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
int numasc = numascs();
struct thr_info *thr;
int asc;
int id;
int i;

if (numasc == 0) {
message(io_data, MSG_ASCNON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numasc) {
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

int dev = ascdevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);

applog(LOG_DEBUG, "API: request to ascenable ascid %d device %d %s%u",


id, dev, cgpu->drv->name, cgpu->device_id);

if (cgpu->deven != DEV_DISABLED) {
message(io_data, MSG_ASCLRENA, id, NULL, isjson);
return;
}

#if 0 /* A DISABLED device wont change status FIXME: should disabling make it WELL?
*/
if (cgpu->status != LIFE_WELL) {
message(io_data, MSG_ASCUNW, id, NULL, isjson);
return;
}
#endif

#ifdef USE_USBUTILS
if (cgpu->usbinfo.nodev) {
message(io_data, MSG_ASCUSBNODEV, id, NULL, isjson);
return;
}
#endif

for (i = 0; i < mining_threads; i++) {


thr = get_thread(i);
asc = thr->cgpu->cgminer_id;
if (asc == dev) {
cgpu->deven = DEV_ENABLED;
applog(LOG_DEBUG, "API: Pushing sem post to thread %d", thr->id);
cgsem_post(&thr->sem);
}
}

message(io_data, MSG_ASCENA, id, NULL, isjson);


}

static void ascdisable(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
int numasc = numascs();
int id;

if (numasc == 0) {
message(io_data, MSG_ASCNON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numasc) {
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

int dev = ascdevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);

applog(LOG_DEBUG, "API: request to ascdisable ascid %d device %d %s%u",


id, dev, cgpu->drv->name, cgpu->device_id);

if (cgpu->deven == DEV_DISABLED) {
message(io_data, MSG_ASCLRDIS, id, NULL, isjson);
return;
}

cgpu->deven = DEV_DISABLED;

message(io_data, MSG_ASCDIS, id, NULL, isjson);


}

static void ascidentify(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
struct device_drv *drv;
int numasc = numascs();
int id;

if (numasc == 0) {
message(io_data, MSG_ASCNON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

id = atoi(param);
if (id < 0 || id >= numasc) {
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

int dev = ascdevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);
drv = cgpu->drv;

if (!drv->identify_device)
message(io_data, MSG_ASCNOID, id, NULL, isjson);
else {
drv->identify_device(cgpu);
message(io_data, MSG_ASCIDENT, id, NULL, isjson);
}
}
#endif

static void asccount(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
bool io_open;
int count = 0;

#ifdef HAVE_AN_ASIC
count = numascs();
#endif

message(io_data, MSG_NUMASC, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_ASCS : _ASCS COMSTR);

root = api_add_int(root, "Count", &count, false);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

#ifdef HAVE_AN_ASIC
static void ascset(struct io_data *io_data, __maybe_unused SOCKETTYPE c,
__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct cgpu_info *cgpu;
struct device_drv *drv;
char buf[TMPBUFSIZ];
int numasc = numascs();

if (numasc == 0) {
message(io_data, MSG_ASCNON, 0, NULL, isjson);
return;
}

if (param == NULL || *param == '\0') {


message(io_data, MSG_MISID, 0, NULL, isjson);
return;
}

char *opt = strchr(param, ',');


if (opt)
*(opt++) = '\0';
if (!opt || !*opt) {
message(io_data, MSG_MISASCOPT, 0, NULL, isjson);
return;
}

int id = atoi(param);
if (id < 0 || id >= numasc) {
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

int dev = ascdevice(id);


if (dev < 0) { // Should never happen
message(io_data, MSG_INVASC, id, NULL, isjson);
return;
}

cgpu = get_devices(dev);
drv = cgpu->drv;

char *set = strchr(opt, ',');


if (set)
*(set++) = '\0';

if (!drv->set_device)
message(io_data, MSG_ASCNOSET, id, NULL, isjson);
else {
char *ret = drv->set_device(cgpu, opt, set, buf);
if (ret) {
if (strcasecmp(opt, "help") == 0)
message(io_data, MSG_ASCHELP, id, ret, isjson);
else
message(io_data, MSG_ASCSETERR, id, ret, isjson);
} else
message(io_data, MSG_ASCSETOK, id, NULL, isjson);
}
}
#endif

static void lcddata(struct io_data *io_data, __maybe_unused SOCKETTYPE c,


__maybe_unused char *param, bool isjson, __maybe_unused char group)
{
struct api_data *root = NULL;
struct cgpu_info *cgpu;
bool io_open;
double ghs = 0.0, last_share_diff = 0.0;
float temp = 0.0;
time_t last_share_time = 0;
time_t last_device_valid_work = 0;
struct pool *pool = NULL;
char *rpc_url = "none", *rpc_user = "";
int i;

message(io_data, MSG_LCD, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_LCD : _LCD COMSTR);

// stop hashmeter() changing some while copying


mutex_lock(&hash_lock);

root = api_add_elapsed(root, "Elapsed", &(total_secs), true);


ghs = total_mhashes_done / total_secs / 1000.0;
root = api_add_mhs(root, "GHS av", &ghs, true);
ghs = rolling5 / 1000.0;
root = api_add_mhs(root, "GHS 5m", &ghs, true);
ghs = total_rolling / 1000.0;
root = api_add_mhs(root, "GHS 5s", &ghs, true);

mutex_unlock(&hash_lock);

temp = 0;
last_device_valid_work = 0;
for (i = 0; i < total_devices; i++) {
cgpu = get_devices(i);
if (last_device_valid_work == 0 ||
last_device_valid_work < cgpu->last_device_valid_work)
last_device_valid_work = cgpu->last_device_valid_work;
if (temp < cgpu->temp)
temp = cgpu->temp;
}

last_share_time = 0;
last_share_diff = 0;
for (i = 0; i < total_pools; i++) {
pool = pools[i];

if (pool->removed)
continue;

if (last_share_time == 0 || last_share_time < pool->last_share_time) {


last_share_time = pool->last_share_time;
last_share_diff = pool->last_share_diff;
}
}
pool = current_pool();
if (pool) {
rpc_url = pool->rpc_url;
rpc_user = pool->rpc_user;
}

root = api_add_temp(root, "Temperature", &temp, false);


root = api_add_diff(root, "Last Share Difficulty", &last_share_diff, false);
root = api_add_time(root, "Last Share Time", &last_share_time, false);
root = api_add_uint64(root, "Best Share", &best_diff, true);
root = api_add_time(root, "Last Valid Work", &last_device_valid_work, false);
root = api_add_uint(root, "Found Blocks", &found_blocks, true);
root = api_add_escape(root, "Current Pool", rpc_url, true);
root = api_add_escape(root, "User", rpc_user, true);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
io_close(io_data);
}

static void checkcommand(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char


*param, bool isjson, char group);

struct CMDS {
char *name;
void (*func)(struct io_data *, SOCKETTYPE, char *, bool, char);
bool iswritemode;
bool joinable;
} cmds[] = {
{ "version", apiversion, false, true },
{ "config", minerconfig, false, true },
{ "devs", devstatus, false, true },
{ "edevs", edevstatus, false, true },
{ "pools", poolstatus, false, true },
{ "summary", summary, false, true },
#ifdef HAVE_AN_FPGA
{ "pga", pgadev, false, false },
{ "pgaenable", pgaenable, true, false },
{ "pgadisable", pgadisable, true, false },
{ "pgaidentify", pgaidentify, true, false },
#endif
{ "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 },
{ "dbgstats", minerdebug, false, true },
{ "estats", minerestats, 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, false },
#endif
{ "zero", dozero, true, false },
{ "hotplug", dohotplug, true, false },
#ifdef HAVE_AN_ASIC
{ "asc", ascdev, false, false },
{ "ascenable", ascenable, true, false },
{ "ascdisable", ascdisable, true, false },
{ "ascidentify", ascidentify, true, false },
{ "ascset", ascset, true, false },
#endif
{ "asccount", asccount, false, true },
{ "lcd", lcddata, 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)
{
struct api_data *root = NULL;
bool io_open;
char cmdbuf[100];
bool found, access;
int i;

if (param == NULL || *param == '\0') {


message(io_data, 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;
}
}

message(io_data, MSG_CHECK, 0, NULL, isjson);


io_open = io_add(io_data, isjson ? COMSTR JSON_CHECK : _CHECK COMSTR);

root = api_add_const(root, "Exists", found ? YES : NO, false);


root = api_add_const(root, "Access", access ? YES : NO, false);

root = print_data(io_data, root, isjson, false);


if (isjson && io_open)
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;
char *buf = io_data->ptr;

strcpy(buf, io_data->ptr);

if (io_data->close)
strcat(buf, JSON_CLOSE);

if (isjson)
strcat(buf, JSON_END);

len = strlen(buf);
tosend = len+1;

applog(LOG_DEBUG, "API: send reply: (%d) '%.10s%s'", tosend, buf, len > 10 ?
"..." : BLANK);

count = sendc = 0;
while (count < 5 && tosend > 0) {
// allow 50ms per attempt
struct timeval timeout = {0, 50000};
fd_set wd;

FD_ZERO(&wd);
FD_SET(c, &wd);
if ((res = select(c + 1, NULL, &wd, NULL, &timeout)) < 1) {
applog(LOG_WARNING, "API: send select failed (%d)", res);
return;
}

n = send(c, buf, tosend, 0);


sendc++;

if (SOCKETFAIL(n)) {
count++;
if (sock_blocks())
continue;

applog(LOG_WARNING, "API: send (%d:%d) failed: %s", len+1, (len+1


- tosend), SOCKERRMSG);

return;
} else {
if (sendc <= 1) {
if (n == tosend)
applog(LOG_DEBUG, "API: sent all of %d first go",
tosend);
else
applog(LOG_DEBUG, "API: sent %d of %d first go", n,
tosend);
} else {
if (n == tosend)
applog(LOG_DEBUG, "API: sent all of remaining %d
(sendc=%d)", tosend, sendc);
else
applog(LOG_DEBUG, "API: sent %d of remaining %d
(sendc=%d)", n, tosend, sendc);
}

tosend -= n;
buf += n;

if (n == 0)
count++;
}
}
}

static void tidyup(__maybe_unused void *arg)


{
mutex_lock(&quit_restart_lock);

SOCKETTYPE *apisock = (SOCKETTYPE *)arg;

bye = true;

if (*apisock != INVSOCK) {
shutdown(*apisock, SHUT_RDWR);
CLOSESOCKET(*apisock);
*apisock = INVSOCK;
}

if (ipaccess != NULL) {
free(ipaccess);
ipaccess = NULL;
}

io_free();

mutex_unlock(&quit_restart_lock);
}

/*
* 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 = cgmalloc(strlen(api_groups) + 1);


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';
quit(1, "API invalid group name '%s'", ptr);
}

group = GROUP(*ptr);
if (!VALIDGROUP(group))
quit(1, "API invalid group name '%c'", *ptr);

if (group == PRIVGROUP)
quit(1, "API group name can't be '%c'", PRIVGROUP);

if (group == NOPRIVGROUP)
quit(1, "API group name can't be '%c'", NOPRIVGROUP);

if (apigroups[GROUPOFFSET(group)].commands != NULL)
quit(1, "API duplicate group name '%c'", *ptr);

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 {
quit(1, "API unknown command '%s' in group '%c'",
ptr, group);
}
}

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 =
cgmalloc(strlen(commands) + 1);

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 =
cgmalloc(strlen(commands) + 1);

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
* ipv6 address should be enclosed with a pair of square brackets and the prefix
left outside
* special case of 0/0 allows /0 (means all IP addresses)
*/
#define ALLIP "0/0"
/*
* N.B. IP4 addresses are by Definition 32bit big endian on all platforms
*/
static void setup_ipaccess()
{
char *buf, *ptr, *comma, *slash, *end, *dot;
int ipcount, mask, i, shift;
char tmp[64], original[64];
bool ipv6 = false;
char group;

buf = cgmalloc(strlen(opt_api_allow) + 1);

strcpy(buf, opt_api_allow);

ipcount = 1;
ptr = buf;
while (*ptr)
if (*(ptr++) == ',')
ipcount++;

// possibly more than needed, but never less


ipaccess = cgcalloc(ipcount, sizeof(struct IPACCESS));

ips = 0;
ptr = buf;
while (ptr && *ptr) {
while (*ptr == ' ' || *ptr == '\t')
ptr++;

if (*ptr == ',') {
ptr++;
continue;
}

comma = strchr(ptr, ',');


if (comma)
*(comma++) = '\0';

strncpy(original, ptr, sizeof(original));


original[sizeof(original)-1] = '\0';
group = NOPRIVGROUP;

if (isalpha(*ptr) && *(ptr+1) == ':') {


if (DEFINEDGROUP(*ptr))
group = GROUP(*ptr);

ptr += 2;
}

ipaccess[ips].group = group;
if (strcmp(ptr, ALLIP) == 0) {
for (i = 0; i < 16; i++) {
ipaccess[ips].ip.s6_addr[i] = 0;
ipaccess[ips].mask.s6_addr[i] = 0;
}
}
else {
end = strchr(ptr, '/');
if (!end) {
for (i = 0; i < 16; i++)
ipaccess[ips].mask.s6_addr[i] = 0xff;
end = ptr + strlen(ptr);
}
slash = end--;
if (*ptr == '[' && *end == ']') {
*(ptr++) = '\0';
*(end--) = '\0';
ipv6 = true;
}
else
ipv6 = false;
if (*slash) {
*(slash++) = '\0';
mask = atoi(slash);
if (mask < 1 || (mask += ipv6 ? 0 : 96) > 128) {
applog(LOG_ERR, "API: ignored address with "
"invalid mask (%d) '%s'",
mask, original);
goto popipo; // skip invalid/zero
}

for (i = 0; i < 16; i++)


ipaccess[ips].mask.s6_addr[i] = 0;

i = 0;
shift = 7;
while (mask-- > 0) {
ipaccess[ips].mask.s6_addr[i] |= 1 << shift;
if (shift-- == 0) {
i++;
shift = 7;
}
}
}

for (i = 0; i < 16; i++)


ipaccess[ips].ip.s6_addr[i] = 0; // missing default to
'[::]'
if (ipv6) {
if (INET_PTON(AF_INET6, ptr, &(ipaccess[ips].ip)) != 1) {
applog(LOG_ERR, "API: ignored invalid "
"IPv6 address '%s'",
original);
goto popipo;
}
}
else {
/* v4 mapped v6 address,
* such as "::ffff:255.255.255.255"
* but pad on extra missing .0 as needed */
dot = strchr(ptr, '.');
if (!dot) {
snprintf(tmp, sizeof(tmp),
"::ffff:%s.0.0.0",
ptr);
} else {
dot = strchr(dot+1, '.');
if (!dot) {
snprintf(tmp, sizeof(tmp),
"::ffff:%s.0.0",
ptr);
} else {
dot = strchr(dot+1, '.');
if (!dot) {
snprintf(tmp, sizeof(tmp),
"::ffff:%s.0",
ptr);
} else {
snprintf(tmp, sizeof(tmp),
"::ffff:%s",
ptr);
}
}
}
if (INET_PTON(AF_INET6, tmp, &(ipaccess[ips].ip)) != 1) {
applog(LOG_ERR, "API: ignored invalid "
"IPv4 address '%s' (as %s)",
original, tmp);
goto popipo;
}
}
for (i = 0; i < 16; i++)
ipaccess[ips].ip.s6_addr[i] &=
ipaccess[ips].mask.s6_addr[i];
}

ips++;
popipo:
ptr = comma;
}

free(buf);
}

static void *quit_thread(__maybe_unused void *userdata)


{
// allow thread creator to finish whatever it's doing
mutex_lock(&quit_restart_lock);
mutex_unlock(&quit_restart_lock);

if (opt_debug)
applog(LOG_DEBUG, "API: killing cgminer");

kill_work();

return NULL;
}
static void *restart_thread(__maybe_unused void *userdata)
{
// allow thread creator to finish whatever it's doing
mutex_lock(&quit_restart_lock);
mutex_unlock(&quit_restart_lock);

if (opt_debug)
applog(LOG_DEBUG, "API: restarting cgminer");

app_restart();

return NULL;
}

static bool check_connect(struct sockaddr_storage *cli, char **connectaddr, char


*group)
{
bool addrok = false;
int i, j;
bool match;
char tmp[30];
struct in6_addr client_ip;

*connectaddr = cgmalloc(INET6_ADDRSTRLEN);
getnameinfo((struct sockaddr *)cli, sizeof(*cli),
*connectaddr, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST);

// v4 mapped v6 address, such as "::ffff:255.255.255.255"


if (cli->ss_family == AF_INET) {
sprintf(tmp, "::ffff:%s", *connectaddr);
INET_PTON(AF_INET6, tmp, &client_ip);
}
else
INET_PTON(AF_INET6, *connectaddr, &client_ip);

*group = NOPRIVGROUP;
if (opt_api_allow) {
for (i = 0; i < ips; i++) {
match = true;
for (j = 0; j < 16; j++) {
if ((client_ip.s6_addr[j] & ipaccess[i].mask.s6_addr[j])
!= ipaccess[i].ip.s6_addr[j]) {
match = false;
break;
}
}
if (match) {
addrok = true;
*group = ipaccess[i].group;
break;
}
}
} else {
if (opt_api_network)
addrok = true;
else
addrok = (strcmp(*connectaddr, localaddr) == 0)
|| IN6_IS_ADDR_LOOPBACK(&client_ip);
}

return addrok;
}

static void mcast()


{
struct sockaddr_storage came_from;
time_t bindstart;
char *binderror;
SOCKETTYPE mcast_sock = INVSOCK;
SOCKETTYPE reply_sock = INVSOCK;
socklen_t came_from_siz;
char *connectaddr;
ssize_t rep;
int bound;
int count;
int reply_port;
bool addrok;
char group;

char port_s[10], came_from_port[10];


struct addrinfo hints, *res, *host, *client;

char expect[] = "cgminer-"; // first 8 bytes constant


char *expect_code;
size_t expect_code_len;
char buf[1024];
char replybuf[1024];

sprintf(port_s, "%d", opt_api_mcast_port);


memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
if (getaddrinfo(opt_api_mcast_addr, port_s, &hints, &res) != 0)
quit(1, "Invalid API Multicast Address");
host = res;
while (host != NULL) {
mcast_sock = socket(res->ai_family, SOCK_DGRAM, 0);
if (mcast_sock > 0)
break;
host = host->ai_next;
}
if (mcast_sock == INVSOCK) {
freeaddrinfo(res);
quit(1, "API mcast could not open socket");
}

int optval = 1;
if (SOCKETFAIL(setsockopt(mcast_sock, SOL_SOCKET, SO_REUSEADDR, (void *)
(&optval), sizeof(optval)))) {
applog(LOG_ERR, "API mcast setsockopt SO_REUSEADDR failed (%s)%s",
SOCKERRMSG, MUNAVAILABLE);
goto die;
}

// try for more than 1 minute ... in case the old one hasn't completely gone
yet
bound = 0;
bindstart = time(NULL);
while (bound == 0) {
if (SOCKETFAIL(bind(mcast_sock, host->ai_addr, host->ai_addrlen))) {
binderror = SOCKERRMSG;
if ((time(NULL) - bindstart) > 61)
break;
else
cgsleep_ms(30000);
} else
bound = 1;
}

if (bound == 0) {
applog(LOG_ERR, "API mcast bind to port %d failed (%s)%s",
opt_api_mcast_port, binderror, MUNAVAILABLE);
goto die;
}

switch (host->ai_family) {
case AF_INET: {
struct ip_mreq grp;
memset(&grp, 0, sizeof(grp));
grp.imr_multiaddr.s_addr = ((struct sockaddr_in *)(host-
>ai_addr))->sin_addr.s_addr;
grp.imr_interface.s_addr = INADDR_ANY;

if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IP,
IP_ADD_MEMBERSHIP,
(void *)(&grp), sizeof(grp)))) {
applog(LOG_ERR, "API mcast join failed (%s)%s", SOCKERRMSG,
MUNAVAILABLE);
goto die;
}
break;
}
case AF_INET6: {
struct ipv6_mreq grp;
memcpy(&grp.ipv6mr_multiaddr, &(((struct sockaddr_in6 *)(host-
>ai_addr))->sin6_addr),
sizeof(struct in6_addr));
grp.ipv6mr_interface= 0;

if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IPV6,
IPV6_ADD_MEMBERSHIP,
(void *)(&grp), sizeof(grp)))) {
applog(LOG_ERR, "API mcast join failed (%s)%s", SOCKERRMSG,
MUNAVAILABLE);
goto die;
}
break;
}
default:
break;
}
freeaddrinfo(res);

expect_code_len = sizeof(expect) + strlen(opt_api_mcast_code);


expect_code = cgmalloc(expect_code_len + 1);
snprintf(expect_code, expect_code_len+1, "%s%s-", expect,
opt_api_mcast_code);
count = 0;
while (80085) {
cgsleep_ms(1000);

count++;
came_from_siz = sizeof(came_from);
if (SOCKETFAIL(rep = recvfrom(mcast_sock, buf, sizeof(buf) - 1,
0, (struct sockaddr *)(&came_from),
&came_from_siz))) {
applog(LOG_DEBUG, "API mcast failed count=%d (%s) (%d)",
count, SOCKERRMSG, (int)mcast_sock);
continue;
}

addrok = check_connect(&came_from, &connectaddr, &group);


applog(LOG_DEBUG, "API mcast from %s - %s",
connectaddr, addrok ? "Accepted" : "Ignored");
if (!addrok)
continue;

buf[rep] = '\0';
if (rep > 0 && buf[rep-1] == '\n')
buf[--rep] = '\0';

getnameinfo((struct sockaddr *)(&came_from), came_from_siz,


NULL, 0, came_from_port, sizeof(came_from_port),
NI_NUMERICHOST);

applog(LOG_DEBUG, "API mcast request rep=%d (%s) from [%s]:%s",


(int)rep, buf, connectaddr, came_from_port);

if ((size_t)rep > expect_code_len && memcmp(buf, expect_code,


expect_code_len) == 0) {
reply_port = atoi(&buf[expect_code_len]);
if (reply_port < 1 || reply_port > 65535) {
applog(LOG_DEBUG, "API mcast request ignored - invalid port
(%s)",
&buf[expect_code_len]);
} else {
applog(LOG_DEBUG, "API mcast request OK port %s=%d",
&buf[expect_code_len], reply_port);

if (getaddrinfo(connectaddr, &buf[expect_code_len], &hints,


&res) != 0) {
applog(LOG_ERR, "Invalid client address %s",
connectaddr);
continue;
}
client = res;
while (client) {
reply_sock = socket(res->ai_family, SOCK_DGRAM, 0);
if (mcast_sock > 0)
break;
client = client->ai_next;
}
if (reply_sock == INVSOCK) {
freeaddrinfo(res);
applog(LOG_ERR, "API mcast could not open socket to
client %s", connectaddr);
continue;
}

snprintf(replybuf, sizeof(replybuf),
"cgm-" API_MCAST_CODE "-%d-%s",
opt_api_port, opt_api_mcast_des);

rep = sendto(reply_sock, replybuf, strlen(replybuf)+1,


0, client->ai_addr, client->ai_addrlen);
freeaddrinfo(res);
if (SOCKETFAIL(rep)) {
applog(LOG_DEBUG, "API mcast send reply failed (%s)
(%d)",
SOCKERRMSG, (int)reply_sock);
} else {
applog(LOG_DEBUG, "API mcast send reply (%s)
succeeded (%d) (%d)",
replybuf, (int)rep,
(int)reply_sock);
}

CLOSESOCKET(reply_sock);
}
} else
applog(LOG_DEBUG, "API mcast request was no good");
}

die:

CLOSESOCKET(mcast_sock);
}

static void *mcast_thread(void *userdata)


{
struct thr_info *mythr = userdata;

pthread_detach(pthread_self());
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

RenameThread("APIMcast");

mcast();

PTH(mythr) = 0L;

return NULL;
}

void mcast_init()
{
struct thr_info *thr;

thr = cgcalloc(1, sizeof(*thr));

if (thr_info_create(thr, NULL, mcast_thread, thr))


quit(1, "API mcast thread create failed");
}
void api(int api_thr_id)
{
struct io_data *io_data;
struct thr_info bye_thr;
char buf[TMPBUFSIZ];
char param_buf[TMPBUFSIZ];
SOCKETTYPE c;
int n, bound;
char *connectaddr;
char *binderror;
time_t bindstart;
short int port = opt_api_port;
char port_s[10];
struct sockaddr_storage cli;
socklen_t clisiz;
char cmdbuf[100];
char *cmd = NULL;
char *param;
bool addrok;
char group;
json_error_t json_err;
json_t *json_config;
json_t *json_val;
bool isjson;
bool did, isjoin, firstjoin;
int i;
struct addrinfo hints, *res, *host;
SOCKETTYPE *apisock;

apisock = cgmalloc(sizeof(*apisock));
*apisock = INVSOCK;
json_config = NULL;
isjoin = false;

if (!opt_api_listen) {
applog(LOG_DEBUG, "API not running%s", UNAVAILABLE);
free(apisock);
return;
}

io_data = sock_io_new();

mutex_init(&quit_restart_lock);

pthread_cleanup_push(tidyup, (void *)apisock);


my_thr_id = api_thr_id;

setup_groups();

if (opt_api_allow) {
setup_ipaccess();

if (ips == 0) {
applog(LOG_WARNING, "API not running (no valid IPs specified)%s",
UNAVAILABLE);
free(apisock);
return;
}
}
/* This should be done before curl in needed
* to ensure curl has already called WSAStartup() in windows */
cgsleep_ms(opt_log_interval*1000);

sprintf(port_s, "%d", port);


memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
if (getaddrinfo(opt_api_host, port_s, &hints, &res) != 0) {
applog(LOG_ERR, "API failed to resolve %s", opt_api_host);
free(apisock);
return;
}
host = res;
while (host) {
*apisock = socket(res->ai_family, SOCK_STREAM, 0);
if (*apisock > 0)
break;
host = host->ai_next;
}
if (*apisock == INVSOCK) {
applog(LOG_ERR, "API initialisation failed (%s)%s", SOCKERRMSG,
UNAVAILABLE);
freeaddrinfo(res);
free(apisock);
return;
}

#ifndef WIN32
// On linux with SO_REUSEADDR, bind will get the port if the previous
// socket is closed (even if it is still in TIME_WAIT) but fail if
// another program has it open - which is what we want
int optval = 1;
// If it doesn't work, we don't really care - just show a debug message
if (SOCKETFAIL(setsockopt(*apisock, SOL_SOCKET, SO_REUSEADDR, (void *)
(&optval), sizeof(optval))))
applog(LOG_DEBUG, "API setsockopt SO_REUSEADDR failed (ignored): %s",
SOCKERRMSG);
#else
// On windows a 2nd program can bind to a port>1024 already in use unless
// SO_EXCLUSIVEADDRUSE is used - however then the bind to a closed port
// in TIME_WAIT will fail until the timeout - so we leave the options alone
#endif

// try for more than 1 minute ... in case the old one hasn't completely gone
yet
bound = 0;
bindstart = time(NULL);
while (bound == 0) {
if (SOCKETFAIL(bind(*apisock, host->ai_addr, host->ai_addrlen))) {
binderror = SOCKERRMSG;
if ((time(NULL) - bindstart) > 61)
break;
else {
applog(LOG_WARNING, "API bind to port %d failed - trying
again in 30sec", port);
cgsleep_ms(30000);
}
} else
bound = 1;
}
freeaddrinfo(res);

if (bound == 0) {
applog(LOG_ERR, "API bind to port %d failed (%s)%s", port, binderror,
UNAVAILABLE);
free(apisock);
return;
}

if (SOCKETFAIL(listen(*apisock, QUEUE))) {
applog(LOG_ERR, "API3 initialisation failed (%s)%s", SOCKERRMSG,
UNAVAILABLE);
CLOSESOCKET(*apisock);
free(apisock);
return;
}

if (opt_api_allow)
applog(LOG_WARNING, "API running in IP access mode on port %d (%d)",
port, (int)*apisock);
else {
if (opt_api_network)
applog(LOG_WARNING, "API running in UNRESTRICTED read access mode
on port %d (%d)", port, (int)*apisock);
else
applog(LOG_WARNING, "API running in local read access mode on
port %d (%d)", port, (int)*apisock);
}

if (opt_api_mcast)
mcast_init();

strbufs = k_new_list("StrBufs", sizeof(SBITEM), ALLOC_SBITEMS, LIMIT_SBITEMS,


false);

while (!bye) {
clisiz = sizeof(cli);
if (SOCKETFAIL(c = accept(*apisock, (struct sockaddr *)(&cli),
&clisiz))) {
applog(LOG_ERR, "API failed (%s)%s (%d)", SOCKERRMSG,
UNAVAILABLE, (int)*apisock);
goto die;
}

addrok = check_connect((struct sockaddr_storage *)&cli, &connectaddr,


&group);
applog(LOG_DEBUG, "API: connection from %s - %s",
connectaddr, addrok ? "Accepted" : "Ignored");

if (addrok) {
/* Accept only half the TMPBUFSIZ to account for space
* potentially used by escaping chars. */
n = recv(c, &buf[0], TMPBUFSIZ / 2 - 1, 0);
if (SOCKETFAIL(n))
buf[0] = '\0';
else
buf[n] = '\0';

if (opt_debug) {
if (SOCKETFAIL(n))
applog(LOG_DEBUG, "API: recv failed: %s",
SOCKERRMSG);
else
applog(LOG_DEBUG, "API: recv command: (%d) '%s'", n,
buf);
}

if (!SOCKETFAIL(n)) {
// the time of the request in now
when = time(NULL);
io_reinit(io_data);

did = false;

if (*buf != ISJSON) {
isjson = false;

param = strchr(buf, SEPARATOR);


if (param != NULL)
*(param++) = '\0';

cmd = buf;
}
else {
isjson = true;

param = NULL;

json_config = json_loadb(buf, n, 0, &json_err);

if (!json_is_object(json_config)) {
message(io_data, MSG_INVJSON, 0, NULL, isjson);
send_result(io_data, c, isjson);
did = true;
} else {
json_val = json_object_get(json_config,
JSON_COMMAND);
if (json_val == NULL) {
message(io_data, MSG_MISCMD, 0, NULL,
isjson);
send_result(io_data, c, isjson);
did = true;
} else {
if (!json_is_string(json_val)) {
message(io_data, MSG_INVCMD, 0,
NULL, isjson);
send_result(io_data, c, isjson);
did = true;
} else {
cmd = (char
*)json_string_value(json_val);
json_val =
json_object_get(json_config, JSON_PARAMETER);
if (json_is_string(json_val))
param = (char
*)json_string_value(json_val);
else if (json_is_integer(json_val))
{
sprintf(param_buf, "%d",
(int)json_integer_value(json_val));
param = param_buf;
} else if (json_is_real(json_val))
{
sprintf(param_buf, "%f",
(double)json_real_value(json_val));
param = param_buf;
}
}
}
}
}

if (!did) {
char *cmdptr, *cmdsbuf = NULL;

if (strchr(cmd, CMDJOIN)) {
firstjoin = isjoin = true;
// cmd + leading+tailing '|' + '\0'
cmdsbuf = cgmalloc(strlen(cmd) + 3);
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;
}
}

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 (isjoin)
send_result(io_data, c, isjson);

if (isjson && json_is_object(json_config))


json_decref(json_config);
}
}
CLOSESOCKET(c);
}
die:
/* Blank line fix for older compilers since pthread_cleanup_pop is a
* macro that gets confused by a label existing immediately before it
*/
;
pthread_cleanup_pop(true);

free(apisock);
if (opt_debug)
applog(LOG_DEBUG, "API: terminating due to: %s",
do_a_quit ? "QUIT" : (do_a_restart ? "RESTART" : (bye ?
"BYE" : "UNKNOWN!")));

mutex_lock(&quit_restart_lock);

if (do_a_restart) {
if (thr_info_create(&bye_thr, NULL, restart_thread, &bye_thr)) {
mutex_unlock(&quit_restart_lock);
quit(1, "API failed to initiate a restart - aborting");
}
pthread_detach(bye_thr.pth);
} else if (do_a_quit) {
if (thr_info_create(&bye_thr, NULL, quit_thread, &bye_thr)) {
mutex_unlock(&quit_restart_lock);
quit(1, "API failed to initiate a clean quit - aborting");
}
pthread_detach(bye_thr.pth);
}

mutex_unlock(&quit_restart_lock);
}

You might also like