Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
litecoincash-project
GitHub Repository: litecoincash-project/cpuminer-multi
Path: blob/master/api.c
548 views
1
/*
2
* Copyright 2014 ccminer team
3
*
4
* Implementation by tpruvot (based on cgminer)
5
*
6
* This program is free software; you can redistribute it and/or modify it
7
* under the terms of the GNU General Public License as published by the Free
8
* Software Foundation; either version 2 of the License, or (at your option)
9
* any later version. See COPYING for more details.
10
*/
11
#define APIVERSION "1.1"
12
13
#ifdef WIN32
14
# define _WINSOCK_DEPRECATED_NO_WARNINGS
15
# include <winsock2.h>
16
#endif
17
18
#include <stdio.h>
19
#include <ctype.h>
20
#include <stdlib.h>
21
#include <string.h>
22
#include <stdbool.h>
23
#include <inttypes.h>
24
#include <unistd.h>
25
#include <sys/time.h>
26
#include <time.h>
27
#include <math.h>
28
#include <stdarg.h>
29
#include <assert.h>
30
31
#include <sys/stat.h>
32
#include <sys/types.h>
33
34
#include "miner.h"
35
36
#ifndef WIN32
37
# include <errno.h>
38
# include <sys/socket.h>
39
# include <netinet/in.h>
40
# include <arpa/inet.h>
41
# include <netdb.h>
42
# define SOCKETTYPE long
43
# define SOCKETFAIL(a) ((a) < 0)
44
# define INVSOCK -1 /* INVALID_SOCKET */
45
# define INVINETADDR -1 /* INADDR_NONE */
46
# define CLOSESOCKET close
47
# define SOCKETINIT {}
48
# define SOCKERRMSG strerror(errno)
49
#else
50
# define SOCKETTYPE SOCKET
51
# define SOCKETFAIL(a) ((a) == SOCKET_ERROR)
52
# define INVSOCK INVALID_SOCKET
53
# define INVINETADDR INADDR_NONE
54
# define CLOSESOCKET closesocket
55
# define in_addr_t uint32_t
56
#endif
57
58
#define GROUP(g) (toupper(g))
59
#define PRIVGROUP GROUP('W')
60
#define NOPRIVGROUP GROUP('R')
61
#define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP)
62
#define GROUPOFFSET(g) (GROUP(g) - GROUP('A'))
63
#define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z'))
64
#define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands)
65
#define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL)
66
struct APIGROUPS {
67
// This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search
68
char *commands;
69
} apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs)
70
71
struct IP4ACCESS {
72
in_addr_t ip;
73
in_addr_t mask;
74
char group;
75
};
76
77
static int ips = 1;
78
static struct IP4ACCESS *ipaccess = NULL;
79
80
// Socket data buffers
81
#define MYBUFSIZ 16384
82
#define SOCK_REC_BUFSZ 1024
83
84
// Socket is on 127.0.0.1
85
#define QUEUE 10
86
87
#define ALLIP4 "0.0.0.0"
88
89
static const char *localaddr = "127.0.0.1";
90
static const char *UNAVAILABLE = " - API will not be available";
91
static char *buffer = NULL;
92
static time_t startup = 0;
93
static int bye = 0;
94
95
extern char *opt_api_allow;
96
extern int opt_api_listen; /* port */
97
extern int opt_api_remote;
98
extern uint64_t global_hashrate;
99
extern uint32_t solved_count;
100
extern uint32_t accepted_count;
101
extern uint32_t rejected_count;
102
103
#define cpu_threads opt_n_threads
104
105
#define USE_MONITORING
106
extern float cpu_temp(int);
107
extern uint32_t cpu_clock(int);
108
extern int cpu_fanpercent(void);
109
110
/***************************************************************/
111
112
static void cpustatus(int thr_id)
113
{
114
if (thr_id >= 0 && thr_id < opt_n_threads) {
115
struct cpu_info *cpu = &thr_info[thr_id].cpu;
116
char buf[512]; *buf = '\0';
117
118
cpu->thr_id = thr_id;
119
cpu->khashes = thr_hashrates[thr_id] / 1000.0; //todo: stats_get_speed(thr_id, 0.0) / 1000.0;
120
121
snprintf(buf, sizeof(buf), "CPU=%d;KHS=%.2f|", thr_id, cpu->khashes);
122
123
// append to buffer
124
strcat(buffer, buf);
125
}
126
}
127
128
/*****************************************************************************/
129
130
/**
131
* Returns miner global infos
132
*/
133
static char *getsummary(char *params)
134
{
135
char algo[64]; *algo = '\0';
136
time_t ts = time(NULL);
137
double uptime = difftime(ts, startup);
138
double accps = (60.0 * accepted_count) / (uptime ? uptime : 1.0);
139
140
struct cpu_info cpu = { 0 };
141
#ifdef USE_MONITORING
142
cpu.has_monitoring = true;
143
cpu.cpu_temp = cpu_temp(0);
144
cpu.cpu_fan = cpu_fanpercent();
145
cpu.cpu_clock = cpu_clock(0);
146
#endif
147
148
get_currentalgo(algo, sizeof(algo));
149
150
*buffer = '\0';
151
sprintf(buffer, "NAME=%s;VER=%s;API=%s;"
152
"ALGO=%s;CPUS=%d;KHS=%.2f;SOLV=%d;ACC=%d;REJ=%d;"
153
"ACCMN=%.3f;DIFF=%.6f;TEMP=%.1f;FAN=%d;FREQ=%d;"
154
"UPTIME=%.0f;TS=%u|",
155
PACKAGE_NAME, PACKAGE_VERSION, APIVERSION,
156
algo, opt_n_threads, (double)global_hashrate / 1000.0,
157
solved_count, accepted_count, rejected_count, accps, net_diff > 0. ? net_diff : stratum_diff,
158
cpu.cpu_temp, cpu.cpu_fan, cpu.cpu_clock,
159
uptime, (uint32_t) ts);
160
return buffer;
161
}
162
163
/**
164
* Returns cpu/thread specific stats
165
*/
166
static char *getthreads(char *params)
167
{
168
*buffer = '\0';
169
for (int i = 0; i < opt_n_threads; i++)
170
cpustatus(i);
171
return buffer;
172
}
173
174
/**
175
* Is remote control allowed ?
176
*/
177
static bool check_remote_access(void)
178
{
179
return (opt_api_remote > 0);
180
}
181
182
/**
183
* Change pool url (see --url parameter)
184
* seturl|stratum+tcp://XeVrkPrWB7pDbdFLfKhF1Z3xpqhsx6wkH3:X@stratum+tcp://mine.xpool.ca:1131|
185
* seturl|stratum+tcp://Danila.1:[email protected]:3335|
186
*/
187
extern bool stratum_need_reset;
188
static char *remote_seturl(char *params)
189
{
190
*buffer = '\0';
191
if (!check_remote_access())
192
return buffer;
193
parse_arg('o', params);
194
stratum_need_reset = true;
195
sprintf(buffer, "%s", "ok|");
196
return buffer;
197
}
198
199
/**
200
* Ask the miner to quit
201
*/
202
static char *remote_quit(char *params)
203
{
204
*buffer = '\0';
205
if (!check_remote_access())
206
return buffer;
207
bye = 1;
208
sprintf(buffer, "%s", "bye|");
209
return buffer;
210
}
211
212
static char *gethelp(char *params);
213
struct CMDS {
214
const char *name;
215
char *(*func)(char *);
216
} cmds[] = {
217
{ "summary", getsummary },
218
{ "threads", getthreads },
219
/* remote functions */
220
{ "seturl", remote_seturl },
221
{ "quit", remote_quit },
222
/* keep it the last */
223
{ "help", gethelp },
224
};
225
#define CMDMAX ARRAY_SIZE(cmds)
226
227
static char *gethelp(char *params)
228
{
229
*buffer = '\0';
230
char * p = buffer;
231
for (int i = 0; i < CMDMAX-1; i++)
232
p += sprintf(p, "%s\n", cmds[i].name);
233
sprintf(p, "|");
234
return buffer;
235
}
236
237
238
static int send_result(SOCKETTYPE c, char *result)
239
{
240
int n;
241
if (!result) {
242
n = (int) send(c, "", 1, 0);
243
} else {
244
// ignore failure - it's closed immediately anyway
245
n = (int) send(c, result, (int) strlen(result) + 1, 0);
246
}
247
return n;
248
}
249
250
/* ---- Base64 Encoding/Decoding Table --- */
251
static const char table64[]=
252
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
253
254
static size_t base64_encode(const uchar *indata, size_t insize, char *outptr, size_t outlen)
255
{
256
uchar ibuf[3];
257
uchar obuf[4];
258
int i, inputparts, inlen = (int) insize;
259
size_t len = 0;
260
char *output, *outbuf;
261
262
memset(outptr, 0, outlen);
263
264
outbuf = output = (char*)calloc(1, inlen * 4 / 3 + 4);
265
if (outbuf == NULL) {
266
return -1;
267
}
268
269
while (inlen > 0) {
270
for (i = inputparts = 0; i < 3; i++) {
271
if (inlen > 0) {
272
inputparts++;
273
ibuf[i] = (uchar) *indata;
274
indata++; inlen--;
275
}
276
else
277
ibuf[i] = 0;
278
}
279
280
obuf[0] = (uchar) ((ibuf[0] & 0xFC) >> 2);
281
obuf[1] = (uchar) (((ibuf[0] & 0x03) << 4) | ((ibuf[1] & 0xF0) >> 4));
282
obuf[2] = (uchar) (((ibuf[1] & 0x0F) << 2) | ((ibuf[2] & 0xC0) >> 6));
283
obuf[3] = (uchar) (ibuf[2] & 0x3F);
284
285
switch(inputparts) {
286
case 1: /* only one byte read */
287
snprintf(output, 5, "%c%c==",
288
table64[obuf[0]],
289
table64[obuf[1]]);
290
break;
291
case 2: /* two bytes read */
292
snprintf(output, 5, "%c%c%c=",
293
table64[obuf[0]],
294
table64[obuf[1]],
295
table64[obuf[2]]);
296
break;
297
default:
298
snprintf(output, 5, "%c%c%c%c",
299
table64[obuf[0]],
300
table64[obuf[1]],
301
table64[obuf[2]],
302
table64[obuf[3]] );
303
break;
304
}
305
if ((len+4) > outlen)
306
break;
307
output += 4; len += 4;
308
}
309
len = snprintf(outptr, len, "%s", outbuf);
310
// todo: seems to be missing on linux
311
if (strlen(outptr) == 27)
312
strcat(outptr, "=");
313
free(outbuf);
314
315
return len;
316
}
317
318
#include "compat/curl-for-windows/openssl/openssl/crypto/sha/sha.h"
319
320
/* websocket handshake (tested in Chrome) */
321
static int websocket_handshake(SOCKETTYPE c, char *result, char *clientkey)
322
{
323
char answer[256];
324
char inpkey[128] = { 0 };
325
char seckey[64];
326
uchar sha1[20];
327
SHA_CTX ctx;
328
329
if (opt_protocol)
330
applog(LOG_DEBUG, "clientkey: %s", clientkey);
331
332
sprintf(inpkey, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", clientkey);
333
334
// SHA-1 test from rfc, returns in base64 "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
335
//sprintf(inpkey, "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
336
337
SHA1_Init(&ctx);
338
SHA1_Update(&ctx, inpkey, strlen(inpkey));
339
SHA1_Final(sha1, &ctx);
340
341
base64_encode(sha1, 20, seckey, sizeof(seckey));
342
343
sprintf(answer,
344
"HTTP/1.1 101 Switching Protocol\r\n"
345
"Upgrade: WebSocket\r\nConnection: Upgrade\r\n"
346
"Sec-WebSocket-Accept: %s\r\n"
347
"Sec-WebSocket-Protocol: text\r\n"
348
"\r\n", seckey);
349
350
// data result as tcp frame
351
352
uchar hd[10] = { 0 };
353
hd[0] = 129; // 0x1 text frame (FIN + opcode)
354
uint64_t datalen = (uint64_t) strlen(result);
355
uint8_t frames = 2;
356
if (datalen <= 125) {
357
hd[1] = (uchar) (datalen);
358
} else if (datalen <= 65535) {
359
hd[1] = (uchar) 126;
360
hd[2] = (uchar) (datalen >> 8);
361
hd[3] = (uchar) (datalen);
362
frames = 4;
363
} else {
364
hd[1] = (uchar) 127;
365
hd[2] = (uchar) (datalen >> 56);
366
hd[3] = (uchar) (datalen >> 48);
367
hd[4] = (uchar) (datalen >> 40);
368
hd[5] = (uchar) (datalen >> 32);
369
hd[6] = (uchar) (datalen >> 24);
370
hd[7] = (uchar) (datalen >> 16);
371
hd[8] = (uchar) (datalen >> 8);
372
hd[9] = (uchar) (datalen);
373
frames = 10;
374
}
375
376
size_t handlen = strlen(answer);
377
uchar *data = (uchar*) calloc(1, handlen + frames + (size_t) datalen + 1);
378
if (data == NULL)
379
return -1;
380
else {
381
uchar *p = data;
382
// HTTP header 101
383
memcpy(p, answer, handlen);
384
p += handlen;
385
// WebSocket Frame - Header + Data
386
memcpy(p, hd, frames);
387
memcpy(p + frames, result, (size_t)datalen);
388
send(c, (const char*)data, (int) (strlen(answer) + frames + (size_t)datalen + 1), 0);
389
free(data);
390
}
391
return 0;
392
}
393
394
/*
395
* N.B. IP4 addresses are by Definition 32bit big endian on all platforms
396
*/
397
static void setup_ipaccess()
398
{
399
char *buf = NULL, *ptr, *comma, *slash, *dot;
400
int ipcount, mask, octet, i;
401
char group;
402
403
buf = (char*) calloc(1, strlen(opt_api_allow) + 1);
404
if (unlikely(!buf))
405
proper_exit(1);//, "Failed to malloc ipaccess buf");
406
407
strcpy(buf, opt_api_allow);
408
ipcount = 1;
409
ptr = buf;
410
while (*ptr) if (*(ptr++) == ',')
411
ipcount++;
412
413
// possibly more than needed, but never less
414
ipaccess = (struct IP4ACCESS *) calloc(ipcount, sizeof(struct IP4ACCESS));
415
if (unlikely(!ipaccess))
416
proper_exit(1);//, "Failed to calloc ipaccess");
417
418
ips = 0;
419
ptr = buf;
420
while (ptr && *ptr) {
421
while (*ptr == ' ' || *ptr == '\t')
422
ptr++;
423
424
if (*ptr == ',') {
425
ptr++;
426
continue;
427
}
428
429
comma = strchr(ptr, ',');
430
if (comma)
431
*(comma++) = '\0';
432
433
group = NOPRIVGROUP;
434
435
if (isalpha(*ptr) && *(ptr+1) == ':') {
436
if (DEFINEDGROUP(*ptr))
437
group = GROUP(*ptr);
438
ptr += 2;
439
}
440
441
ipaccess[ips].group = group;
442
443
if (strcmp(ptr, ALLIP4) == 0)
444
ipaccess[ips].ip = ipaccess[ips].mask = 0;
445
else
446
{
447
slash = strchr(ptr, '/');
448
if (!slash)
449
ipaccess[ips].mask = 0xffffffff;
450
else {
451
*(slash++) = '\0';
452
mask = atoi(slash);
453
if (mask < 1 || mask > 32)
454
goto popipo; // skip invalid/zero
455
456
ipaccess[ips].mask = 0;
457
while (mask-- >= 0) {
458
octet = 1 << (mask % 8);
459
ipaccess[ips].mask |= (octet << (24 - (8 * (mask >> 3))));
460
}
461
}
462
463
ipaccess[ips].ip = 0; // missing default to '.0'
464
for (i = 0; ptr && (i < 4); i++) {
465
dot = strchr(ptr, '.');
466
if (dot)
467
*(dot++) = '\0';
468
octet = atoi(ptr);
469
470
if (octet < 0 || octet > 0xff)
471
goto popipo; // skip invalid
472
473
ipaccess[ips].ip |= (octet << (24 - (i * 8)));
474
475
ptr = dot;
476
}
477
478
ipaccess[ips].ip &= ipaccess[ips].mask;
479
}
480
481
ips++;
482
popipo:
483
ptr = comma;
484
}
485
486
free(buf);
487
}
488
489
static bool check_connect(struct sockaddr_in *cli, char **connectaddr, char *group)
490
{
491
bool addrok = false;
492
493
*connectaddr = inet_ntoa(cli->sin_addr);
494
495
*group = NOPRIVGROUP;
496
if (opt_api_allow) {
497
int client_ip = htonl(cli->sin_addr.s_addr);
498
for (int i = 0; i < ips; i++) {
499
if ((client_ip & ipaccess[i].mask) == ipaccess[i].ip) {
500
addrok = true;
501
*group = ipaccess[i].group;
502
break;
503
}
504
}
505
}
506
else
507
addrok = (strcmp(*connectaddr, localaddr) == 0);
508
509
return addrok;
510
}
511
512
static void api()
513
{
514
const char *addr = opt_api_allow;
515
unsigned short port = (unsigned short) opt_api_listen; // 4048
516
char buf[MYBUFSIZ];
517
int c, n, bound;
518
char *connectaddr;
519
char *binderror;
520
char group;
521
time_t bindstart;
522
struct sockaddr_in serv;
523
struct sockaddr_in cli;
524
socklen_t clisiz;
525
bool addrok = false;
526
long long counter;
527
char *result;
528
char *params;
529
int i;
530
531
SOCKETTYPE *apisock;
532
if (!opt_api_listen && opt_debug) {
533
applog(LOG_DEBUG, "API disabled");
534
return;
535
}
536
537
if (opt_api_allow) {
538
setup_ipaccess();
539
if (ips == 0) {
540
applog(LOG_WARNING, "API not running (no valid IPs specified)%s", UNAVAILABLE);
541
}
542
}
543
544
apisock = (SOCKETTYPE*) calloc(1, sizeof(*apisock));
545
*apisock = INVSOCK;
546
547
sleep(1);
548
549
*apisock = socket(AF_INET, SOCK_STREAM, 0);
550
if (*apisock == INVSOCK) {
551
applog(LOG_ERR, "API initialisation failed (%s)%s", strerror(errno), UNAVAILABLE);
552
return;
553
}
554
555
memset(&serv, 0, sizeof(serv));
556
serv.sin_family = AF_INET;
557
serv.sin_addr.s_addr = inet_addr(addr);
558
if (serv.sin_addr.s_addr == (in_addr_t)INVINETADDR) {
559
applog(LOG_ERR, "API initialisation 2 failed (%s)%s", strerror(errno), UNAVAILABLE);
560
return;
561
}
562
563
serv.sin_port = htons(port);
564
565
#ifndef WIN32
566
// On linux with SO_REUSEADDR, bind will get the port if the previous
567
// socket is closed (even if it is still in TIME_WAIT) but fail if
568
// another program has it open - which is what we want
569
int optval = 1;
570
// If it doesn't work, we don't really care - just show a debug message
571
if (SOCKETFAIL(setsockopt(*apisock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval))))
572
applog(LOG_DEBUG, "API setsockopt SO_REUSEADDR failed (ignored): %s", SOCKERRMSG);
573
#else
574
// On windows a 2nd program can bind to a port>1024 already in use unless
575
// SO_EXCLUSIVEADDRUSE is used - however then the bind to a closed port
576
// in TIME_WAIT will fail until the timeout - so we leave the options alone
577
#endif
578
579
// try for 1 minute ... in case the old one hasn't completely gone yet
580
bound = 0;
581
bindstart = time(NULL);
582
while (bound == 0) {
583
if (bind(*apisock, (struct sockaddr *)(&serv), sizeof(serv)) < 0) {
584
binderror = strerror(errno);
585
if ((time(NULL) - bindstart) > 61)
586
break;
587
else {
588
if (!opt_quiet || opt_debug)
589
applog(LOG_WARNING, "API bind to port %d failed - trying again in 20sec", port);
590
sleep(20);
591
}
592
}
593
else
594
bound = 1;
595
}
596
597
if (bound == 0) {
598
applog(LOG_WARNING, "API bind to port %d failed (%s)%s", port, binderror, UNAVAILABLE);
599
free(apisock);
600
return;
601
}
602
603
if (SOCKETFAIL(listen(*apisock, QUEUE))) {
604
applog(LOG_ERR, "API initialisation 3 failed (%s)%s", strerror(errno), UNAVAILABLE);
605
CLOSESOCKET(*apisock);
606
free(apisock);
607
return;
608
}
609
610
buffer = (char *) calloc(1, MYBUFSIZ + 1);
611
612
counter = 0;
613
while (bye == 0) {
614
counter++;
615
616
clisiz = sizeof(cli);
617
if (SOCKETFAIL(c = accept((SOCKETTYPE)*apisock, (struct sockaddr *)(&cli), &clisiz))) {
618
applog(LOG_ERR, "API failed (%s)%s", strerror(errno), UNAVAILABLE);
619
CLOSESOCKET(*apisock);
620
free(apisock);
621
free(buffer);
622
return;
623
}
624
625
addrok = check_connect(&cli, &connectaddr, &group);
626
if (opt_debug && opt_protocol)
627
applog(LOG_DEBUG, "API: connection from %s - %s",
628
connectaddr, addrok ? "Accepted" : "Ignored");
629
630
if (addrok) {
631
bool fail;
632
char *wskey = NULL;
633
n = recv(c, &buf[0], SOCK_REC_BUFSZ, 0);
634
635
fail = SOCKETFAIL(n);
636
if (fail)
637
buf[0] = '\0';
638
else if (n > 0 && buf[n-1] == '\n') {
639
/* telnet compat \r\n */
640
buf[n-1] = '\0'; n--;
641
if (n > 0 && buf[n-1] == '\r')
642
buf[n-1] = '\0';
643
}
644
if (n >= 0)
645
buf[n] = '\0';
646
647
//if (opt_debug && opt_protocol && n > 0)
648
// applog(LOG_DEBUG, "API: recv command: (%d) '%s'+char(%x)", n, buf, buf[n-1]);
649
650
if (!fail) {
651
char *msg = NULL;
652
/* Websocket requests compat. */
653
if ((msg = strstr(buf, "GET /")) && strlen(msg) > 5) {
654
char cmd[256] = { 0 };
655
sscanf(&msg[5], "%s\n", cmd);
656
params = strchr(cmd, '/');
657
if (params)
658
*(params++) = '|';
659
params = strchr(cmd, '/');
660
if (params)
661
*(params++) = '\0';
662
wskey = strstr(msg, "Sec-WebSocket-Key");
663
if (wskey) {
664
char *eol = strchr(wskey, '\r');
665
if (eol) *eol = '\0';
666
wskey = strchr(wskey, ':');
667
wskey++;
668
while ((*wskey) == ' ') wskey++; // ltrim
669
}
670
n = sprintf(buf, "%s", cmd);
671
}
672
673
params = strchr(buf, '|');
674
if (params != NULL)
675
*(params++) = '\0';
676
677
if (opt_debug && opt_protocol && n > 0)
678
applog(LOG_DEBUG, "API: exec command %s(%s)", buf, params);
679
680
for (i = 0; i < CMDMAX; i++) {
681
if (strcmp(buf, cmds[i].name) == 0) {
682
if (params && strlen(params)) {
683
// remove possible trailing |
684
if (params[strlen(params) - 1] == '|')
685
params[strlen(params) - 1] = '\0';
686
}
687
result = (cmds[i].func)(params);
688
if (wskey) {
689
websocket_handshake(c, result, wskey);
690
break;
691
}
692
send_result(c, result);
693
break;
694
}
695
}
696
CLOSESOCKET(c);
697
}
698
}
699
}
700
701
CLOSESOCKET(*apisock);
702
free(apisock);
703
free(buffer);
704
}
705
706
/* external access */
707
void *api_thread(void *userdata)
708
{
709
struct thr_info *mythr = (struct thr_info*)userdata;
710
711
startup = time(NULL);
712
api();
713
tq_freeze(mythr->q);
714
715
if (bye) {
716
// quit command
717
proper_exit(1);
718
}
719
720
return NULL;
721
}
722
723