You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
469 lines
11 KiB
469 lines
11 KiB
/* |
|
* Copyright 2014 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. |
|
*/ |
|
|
|
#include <sys/socket.h> |
|
#include <sys/stat.h> |
|
#include <sys/types.h> |
|
#include <sys/wait.h> |
|
#include <signal.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
|
|
#include "ckpool.h" |
|
#include "libckpool.h" |
|
#include "generator.h" |
|
#include "stratifier.h" |
|
#include "connector.h" |
|
|
|
ckpool_t *global_ckp; |
|
|
|
static void *listener(void *arg) |
|
{ |
|
proc_instance_t *pi = (proc_instance_t *)arg; |
|
unixsock_t *us = &pi->us; |
|
char *buf = NULL; |
|
int sockd; |
|
|
|
rename_proc(pi->sockname); |
|
retry: |
|
dealloc(buf); |
|
sockd = accept(us->sockd, NULL, NULL); |
|
if (sockd < 0) { |
|
if (interrupted()) |
|
goto retry; |
|
LOGERR("Failed to accept on socket in listener"); |
|
goto out; |
|
} |
|
/* Insert parsing and repeat code here */ |
|
buf = recv_unix_msg(sockd); |
|
if (!buf) { |
|
LOGWARNING("Failed to get message in listener"); |
|
close(sockd); |
|
goto retry; |
|
} |
|
if (!strncasecmp(buf, "shutdown", 8)) { |
|
LOGWARNING("Listener received shutdown message, terminating ckpool"); |
|
close(sockd); |
|
goto out; |
|
} |
|
close(sockd); |
|
goto retry; |
|
out: |
|
dealloc(buf); |
|
close_unix_socket(us->sockd, us->path); |
|
return NULL; |
|
} |
|
|
|
/* Open the file in path, check if there is a pid in there that still exists |
|
* and if not, write the pid into that file. */ |
|
static bool write_pid(ckpool_t *ckp, const char *path, pid_t pid) |
|
{ |
|
struct stat statbuf; |
|
FILE *fp; |
|
int ret; |
|
|
|
if (!stat(path, &statbuf)) { |
|
int oldpid; |
|
|
|
LOGWARNING("File %s exists", path); |
|
fp = fopen(path, "r"); |
|
if (!fp) { |
|
LOGEMERG("Failed to open file %s", path); |
|
return false; |
|
} |
|
ret = fscanf(fp, "%d", &oldpid); |
|
fclose(fp); |
|
if (ret == 1 && !(kill(oldpid, 0))) { |
|
if (!ckp->killold) { |
|
LOGEMERG("Process %s pid %d still exists", path, oldpid); |
|
return false; |
|
} |
|
if (kill(oldpid, 9)) { |
|
LOGEMERG("Unable to kill old process %s pid %d", path, oldpid); |
|
return false; |
|
} |
|
LOGWARNING("Killing off old process %s pid %d", path, oldpid); |
|
} |
|
} |
|
fp = fopen(path, "w"); |
|
if (!fp) { |
|
LOGERR("Failed to open file %s", path); |
|
return false; |
|
} |
|
fprintf(fp, "%d", pid); |
|
fclose(fp); |
|
|
|
return true; |
|
} |
|
|
|
static void create_process_unixsock(proc_instance_t *pi) |
|
{ |
|
unixsock_t *us = &pi->us; |
|
|
|
us->path = strdup(pi->ckp->socket_dir); |
|
realloc_strcat(&us->path, pi->sockname); |
|
LOGDEBUG("Opening %s", us->path); |
|
us->sockd = open_unix_server(us->path); |
|
if (unlikely(us->sockd < 0)) |
|
quit(1, "Failed to open %s socket", pi->sockname); |
|
} |
|
|
|
static void write_namepid(proc_instance_t *pi) |
|
{ |
|
char s[256]; |
|
|
|
pi->pid = getpid(); |
|
sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); |
|
if (!write_pid(pi->ckp, s, pi->pid)) |
|
quit(1, "Failed to write %s pid %d", pi->processname, pi->pid); |
|
} |
|
|
|
static void rm_namepid(proc_instance_t *pi) |
|
{ |
|
char s[256]; |
|
|
|
sprintf(s, "%s%s.pid", pi->ckp->socket_dir, pi->processname); |
|
unlink(s); |
|
} |
|
|
|
/* Disable signal handlers for child processes, but simply pass them onto the |
|
* parent process to shut down cleanly. */ |
|
static void childsighandler(int sig) |
|
{ |
|
pid_t ppid = getppid(); |
|
|
|
kill(ppid, sig); |
|
} |
|
|
|
static void launch_process(proc_instance_t *pi) |
|
{ |
|
pid_t pid; |
|
|
|
pid = fork(); |
|
if (pid < 0) |
|
quit(1, "Failed to fork %s in launch_process", pi->processname); |
|
if (!pid) { |
|
struct sigaction handler; |
|
int ret; |
|
|
|
handler.sa_handler = &childsighandler; |
|
handler.sa_flags = 0; |
|
sigemptyset(&handler.sa_mask); |
|
sigaction(SIGTERM, &handler, NULL); |
|
sigaction(SIGINT, &handler, NULL); |
|
|
|
rename_proc(pi->processname); |
|
write_namepid(pi); |
|
ret = pi->process(pi); |
|
close_unix_socket(pi->us.sockd, pi->us.path); |
|
rm_namepid(pi); |
|
exit(ret); |
|
} |
|
pi->pid = pid; |
|
} |
|
|
|
static void clean_up(ckpool_t *ckp) |
|
{ |
|
rm_namepid(&ckp->main); |
|
dealloc(ckp->socket_dir); |
|
} |
|
|
|
static void __shutdown_children(ckpool_t *ckp, int sig) |
|
{ |
|
pthread_cancel(ckp->pth_watchdog); |
|
join_pthread(ckp->pth_watchdog); |
|
|
|
if (!kill(ckp->generator.pid, 0)) |
|
kill(ckp->generator.pid, sig); |
|
if (!kill(ckp->stratifier.pid, 0)) |
|
kill(ckp->stratifier.pid, sig); |
|
if (!kill(ckp->connector.pid, 0)) |
|
kill(ckp->connector.pid, sig); |
|
} |
|
|
|
static void shutdown_children(ckpool_t *ckp, int sig) |
|
{ |
|
pthread_cancel(ckp->pth_watchdog); |
|
join_pthread(ckp->pth_watchdog); |
|
|
|
__shutdown_children(ckp, sig); |
|
} |
|
|
|
static void sighandler(int sig) |
|
{ |
|
pthread_cancel(global_ckp->pth_watchdog); |
|
join_pthread(global_ckp->pth_watchdog); |
|
|
|
/* First attempt, send a shutdown message */ |
|
send_proc(&global_ckp->generator, "shutdown"); |
|
send_proc(&global_ckp->stratifier, "shutdown"); |
|
send_proc(&global_ckp->connector, "shutdown"); |
|
|
|
if (sig != 9) { |
|
/* Wait a second, then send SIGTERM */ |
|
sleep(1); |
|
__shutdown_children(global_ckp, SIGTERM); |
|
/* Wait another second, then send SIGKILL */ |
|
sleep(1); |
|
__shutdown_children(global_ckp, SIGKILL); |
|
pthread_cancel(global_ckp->pth_listener); |
|
} else { |
|
__shutdown_children(global_ckp, SIGKILL); |
|
exit(1); |
|
} |
|
} |
|
|
|
static void json_get_string(char **store, json_t *val, const char *res) |
|
{ |
|
json_t *entry = json_object_get(val, res); |
|
const char *buf; |
|
|
|
*store = NULL; |
|
if (!entry || json_is_null(entry)) { |
|
LOGDEBUG("Json did not find entry %s", res); |
|
return; |
|
} |
|
if (!json_is_string(entry)) { |
|
LOGWARNING("Json entry %s is not a string", res); |
|
return; |
|
} |
|
buf = json_string_value(entry); |
|
LOGDEBUG("Json found entry %s: %s", res, buf); |
|
*store = strdup(buf); |
|
} |
|
|
|
static void json_get_int(int *store, json_t *val, const char *res) |
|
{ |
|
json_t *entry = json_object_get(val, res); |
|
|
|
if (!entry) { |
|
LOGDEBUG("Json did not find entry %s", res); |
|
return; |
|
} |
|
if (!json_is_integer(entry)) { |
|
LOGWARNING("Json entry %s is not an integer", res); |
|
return; |
|
} |
|
*store = json_integer_value(entry); |
|
} |
|
|
|
static void parse_config(ckpool_t *ckp) |
|
{ |
|
json_error_t err_val; |
|
json_t *json_conf; |
|
|
|
json_conf = json_load_file(ckp->config, JSON_DISABLE_EOF_CHECK, &err_val); |
|
if (!json_conf) { |
|
LOGWARNING("Json decode error for config file %s: (%d): %s", ckp->config, |
|
err_val.line, err_val.text); |
|
return; |
|
} |
|
json_get_string(&ckp->btcdurl, json_conf, "btcdurl"); |
|
json_get_string(&ckp->btcdauth, json_conf, "btcdauth"); |
|
json_get_string(&ckp->btcdpass, json_conf, "btcdpass"); |
|
json_get_string(&ckp->btcaddress, json_conf, "btcaddress"); |
|
json_get_string(&ckp->btcsig, json_conf, "btcsig"); |
|
json_get_int(&ckp->blockpoll, json_conf, "blockpoll"); |
|
json_get_int(&ckp->update_interval, json_conf, "update_interval"); |
|
json_get_string(&ckp->serverurl, json_conf, "serverurl"); |
|
json_get_int(&ckp->mindiff, json_conf, "mindiff"); |
|
json_get_int(&ckp->startdiff, json_conf, "startdiff"); |
|
json_decref(json_conf); |
|
} |
|
|
|
static void test_functions(ckpool_t *ckp) |
|
{ |
|
char *path = ckp->generator.us.path, *buf; |
|
int genfd; |
|
|
|
genfd = open_unix_client(ckp->generator.us.path); |
|
if (genfd < 0) { |
|
LOGWARNING("Failed to open generator socket %s", path); |
|
return; |
|
} |
|
send_unix_msg(genfd, "getbase"); |
|
buf = recv_unix_msg(genfd); |
|
dealloc(buf); |
|
#if 0 |
|
genfd = open_unix_client(ckp->generator.us.path); |
|
if (genfd < 0) { |
|
LOGWARNING("Failed to open generator socket %s", path); |
|
return; |
|
} |
|
send_unix_msg(genfd, "shutdown"); |
|
#endif |
|
} |
|
|
|
static void prepare_generator(ckpool_t *ckp) |
|
{ |
|
proc_instance_t *pi = &ckp->generator; |
|
|
|
pi->ckp = ckp; |
|
pi->processname = strdup("generator"); |
|
pi->sockname = pi->processname; |
|
pi->process = &generator; |
|
create_process_unixsock(pi); |
|
} |
|
|
|
static void prepare_stratifier(ckpool_t *ckp) |
|
{ |
|
proc_instance_t *pi = &ckp->stratifier; |
|
|
|
pi->ckp = ckp; |
|
pi->processname = strdup("stratifier"); |
|
pi->sockname = pi->processname; |
|
pi->process = &stratifier; |
|
create_process_unixsock(pi); |
|
} |
|
|
|
static void prepare_connector(ckpool_t *ckp) |
|
{ |
|
proc_instance_t *pi = &ckp->connector; |
|
|
|
pi->ckp = ckp; |
|
pi->processname = strdup("connector"); |
|
pi->sockname = pi->processname; |
|
pi->process = &connector; |
|
create_process_unixsock(pi); |
|
} |
|
|
|
static void *watchdog(void *arg) |
|
{ |
|
ckpool_t *ckp = (ckpool_t *)arg; |
|
|
|
rename_proc("watchdog"); |
|
while (42) { |
|
int pid, status; |
|
|
|
pid = wait(&status); |
|
if (pid == ckp->generator.pid) { |
|
LOGERR("Generator process dead! Relaunching"); |
|
launch_process(&ckp->generator); |
|
} else if (pid == ckp->stratifier.pid) { |
|
LOGERR("Stratifier process dead! Relaunching"); |
|
launch_process(&ckp->stratifier); |
|
} else if (pid == ckp->connector.pid) { |
|
LOGERR("Connector process dead! Relaunching"); |
|
launch_process(&ckp->connector); |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
struct sigaction handler; |
|
int len, c, ret; |
|
ckpool_t ckp; |
|
|
|
global_ckp = &ckp; |
|
memset(&ckp, 0, sizeof(ckp)); |
|
ckp.loglevel = LOG_NOTICE; |
|
|
|
while ((c = getopt(argc, argv, "c:kl:n:s:")) != -1) { |
|
switch (c) { |
|
case 'c': |
|
ckp.config = optarg; |
|
break; |
|
case 'k': |
|
ckp.killold = true; |
|
break; |
|
case 'l': |
|
ckp.loglevel = atoi(optarg); |
|
if (ckp.loglevel < LOG_EMERG || ckp.loglevel > LOG_DEBUG) { |
|
quit(1, "Invalid loglevel (range %d - %d): %d", |
|
LOG_EMERG, LOG_DEBUG, ckp.loglevel); |
|
} |
|
break; |
|
case 'n': |
|
ckp.name = optarg; |
|
break; |
|
case 's': |
|
ckp.socket_dir = strdup(optarg); |
|
break; |
|
break; |
|
} |
|
} |
|
|
|
if (!ckp.name) |
|
ckp.name = strdup("ckpool"); |
|
if (!ckp.config) { |
|
ckp.config = strdup(ckp.name); |
|
realloc_strcat(&ckp.config, ".conf"); |
|
} |
|
if (!ckp.socket_dir) { |
|
ckp.socket_dir = strdup("/tmp/"); |
|
realloc_strcat(&ckp.socket_dir, ckp.name); |
|
} |
|
len = strlen(ckp.socket_dir); |
|
if (memcmp(&ckp.socket_dir[len], "/", 1)) |
|
realloc_strcat(&ckp.socket_dir, "/"); |
|
|
|
/* Ignore sigpipe */ |
|
signal(SIGPIPE, SIG_IGN); |
|
|
|
ret = mkdir(ckp.socket_dir, 0700); |
|
if (ret && errno != EEXIST) |
|
quit(1, "Failed to make directory %s", ckp.socket_dir); |
|
|
|
parse_config(&ckp); |
|
/* Set defaults if not found in config file */ |
|
if (!ckp.btcdurl) |
|
ckp.btcdurl = strdup("localhost:8332"); |
|
if (!ckp.btcdauth) |
|
ckp.btcdauth = strdup("user"); |
|
if (!ckp.btcdpass) |
|
ckp.btcdpass = strdup("pass"); |
|
if (!ckp.btcaddress) |
|
ckp.btcaddress = strdup("15qSxP1SQcUX3o4nhkfdbgyoWEFMomJ4rZ"); |
|
if (!ckp.blockpoll) |
|
ckp.blockpoll = 500; |
|
if (!ckp.update_interval) |
|
ckp.update_interval = 30; |
|
if (!ckp.mindiff) |
|
ckp.mindiff = 1; |
|
if (!ckp.startdiff) |
|
ckp.startdiff = 42; |
|
|
|
ckp.main.ckp = &ckp; |
|
ckp.main.processname = strdup("main"); |
|
ckp.main.sockname = strdup("listener"); |
|
write_namepid(&ckp.main); |
|
create_process_unixsock(&ckp.main); |
|
|
|
create_pthread(&ckp.pth_listener, listener, &ckp.main); |
|
|
|
/* Launch separate processes from here */ |
|
prepare_generator(&ckp); |
|
prepare_stratifier(&ckp); |
|
prepare_connector(&ckp); |
|
launch_process(&ckp.generator); |
|
launch_process(&ckp.stratifier); |
|
launch_process(&ckp.connector); |
|
|
|
handler.sa_handler = &sighandler; |
|
handler.sa_flags = 0; |
|
sigemptyset(&handler.sa_mask); |
|
sigaction(SIGTERM, &handler, NULL); |
|
sigaction(SIGINT, &handler, NULL); |
|
|
|
test_functions(&ckp); |
|
|
|
create_pthread(&ckp.pth_watchdog, watchdog, &ckp); |
|
|
|
/* Shutdown from here if the listener is sent a shutdown message */ |
|
join_pthread(ckp.pth_listener); |
|
|
|
shutdown_children(&ckp, SIGTERM); |
|
clean_up(&ckp); |
|
|
|
return 0; |
|
}
|
|
|