Changeset View
Changeset View
Standalone View
Standalone View
src/seeder/main.cpp
#include <clientversion.h> | #include <clientversion.h> | ||||
#include <fs.h> | #include <fs.h> | ||||
#include <logging.h> | #include <logging.h> | ||||
#include <protocol.h> | #include <protocol.h> | ||||
#include <seeder/bitcoin.h> | #include <seeder/bitcoin.h> | ||||
#include <seeder/db.h> | #include <seeder/db.h> | ||||
#include <seeder/dns.h> | #include <seeder/dns.h> | ||||
#include <streams.h> | #include <streams.h> | ||||
#include <util/strencodings.h> | |||||
#include <util/system.h> | #include <util/system.h> | ||||
#include <algorithm> | #include <algorithm> | ||||
#include <atomic> | #include <atomic> | ||||
#include <cinttypes> | #include <cinttypes> | ||||
#include <csignal> | #include <csignal> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
#include <getopt.h> | |||||
#include <pthread.h> | #include <pthread.h> | ||||
const std::function<std::string(const char *)> G_TRANSLATION_FUN = nullptr; | const std::function<std::string(const char *)> G_TRANSLATION_FUN = nullptr; | ||||
class CDnsSeedOpts { | |||||
public: | |||||
int nThreads; | |||||
int nPort; | |||||
int nDnsThreads; | |||||
int fUseTestNet; | |||||
int fWipeBan; | |||||
int fWipeIgnore; | |||||
const char *mbox; | |||||
const char *ns; | |||||
const char *host; | |||||
const char *tor; | |||||
const char *ipv4_proxy; | |||||
const char *ipv6_proxy; | |||||
std::set<uint64_t> filter_whitelist; | |||||
CDnsSeedOpts() | |||||
: nThreads(96), nPort(53), nDnsThreads(4), fUseTestNet(false), | |||||
fWipeBan(false), fWipeIgnore(false), mbox(nullptr), ns(nullptr), | |||||
host(nullptr), tor(nullptr), ipv4_proxy(nullptr), | |||||
ipv6_proxy(nullptr) {} | |||||
void ParseCommandLine(int argc, char **argv) { | |||||
static const char *help = | |||||
"Bitcoin-cash-seeder\n" | |||||
"Usage: %s -h <host> -n <ns> [-m <mbox>] [-t <threads>] [-p " | |||||
"<port>]\n" | |||||
"\n" | |||||
"Options:\n" | |||||
"-h <host> Hostname of the DNS seed\n" | |||||
"-n <ns> Hostname of the nameserver\n" | |||||
"-m <mbox> E-Mail address reported in SOA records\n" | |||||
"-t <threads> Number of crawlers to run in parallel (default " | |||||
"96)\n" | |||||
"-d <threads> Number of DNS server threads (default 4)\n" | |||||
"-p <port> UDP port to listen on (default 53)\n" | |||||
"-o <ip:port> Tor proxy IP/Port\n" | |||||
"-i <ip:port> IPV4 SOCKS5 proxy IP/Port\n" | |||||
"-k <ip:port> IPV6 SOCKS5 proxy IP/Port\n" | |||||
"-w f1,f2,... Allow these flag combinations as filters\n" | |||||
"--testnet Use testnet\n" | |||||
"--wipeban Wipe list of banned nodes\n" | |||||
"--wipeignore Wipe list of ignored nodes\n" | |||||
"-?, --help Show this text\n" | |||||
"\n"; | |||||
bool showHelp = false; | |||||
while (1) { | |||||
static struct option long_options[] = { | |||||
{"host", required_argument, 0, 'h'}, | |||||
{"ns", required_argument, 0, 'n'}, | |||||
{"mbox", required_argument, 0, 'm'}, | |||||
{"threads", required_argument, 0, 't'}, | |||||
{"dnsthreads", required_argument, 0, 'd'}, | |||||
{"port", required_argument, 0, 'p'}, | |||||
{"onion", required_argument, 0, 'o'}, | |||||
{"proxyipv4", required_argument, 0, 'i'}, | |||||
{"proxyipv6", required_argument, 0, 'k'}, | |||||
{"filter", required_argument, 0, 'w'}, | |||||
{"testnet", no_argument, &fUseTestNet, 1}, | |||||
{"wipeban", no_argument, &fWipeBan, 1}, | |||||
{"wipeignore", no_argument, &fWipeBan, 1}, | |||||
{"help", no_argument, 0, 'h'}, | |||||
{0, 0, 0, 0}}; | |||||
int option_index = 0; | |||||
int c = | |||||
getopt_long(argc, argv, "h:n:m:t:p:d:o:i:k:w:", long_options, | |||||
&option_index); | |||||
if (c == -1) break; | |||||
switch (c) { | |||||
case 'h': { | |||||
host = optarg; | |||||
break; | |||||
} | |||||
case 'm': { | |||||
mbox = optarg; | |||||
break; | |||||
} | |||||
case 'n': { | |||||
ns = optarg; | |||||
break; | |||||
} | |||||
case 't': { | |||||
int n = strtol(optarg, nullptr, 10); | |||||
if (n > 0 && n < 1000) nThreads = n; | |||||
break; | |||||
} | |||||
case 'd': { | |||||
int n = strtol(optarg, nullptr, 10); | |||||
if (n > 0 && n < 1000) nDnsThreads = n; | |||||
break; | |||||
} | |||||
case 'p': { | |||||
int p = strtol(optarg, nullptr, 10); | |||||
if (p > 0 && p < 65536) nPort = p; | |||||
break; | |||||
} | |||||
case 'o': { | |||||
tor = optarg; | |||||
break; | |||||
} | |||||
case 'i': { | |||||
ipv4_proxy = optarg; | |||||
break; | |||||
} | |||||
case 'k': { | |||||
ipv6_proxy = optarg; | |||||
break; | |||||
} | |||||
case 'w': { | |||||
char *ptr = optarg; | |||||
while (*ptr != 0) { | |||||
unsigned long l = strtoul(ptr, &ptr, 0); | |||||
if (*ptr == ',') { | |||||
ptr++; | |||||
} else if (*ptr != 0) { | |||||
break; | |||||
} | |||||
filter_whitelist.insert(l); | |||||
} | |||||
break; | |||||
} | |||||
case '?': { | |||||
showHelp = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
if (filter_whitelist.empty()) { | |||||
filter_whitelist.insert(NODE_NETWORK); | |||||
filter_whitelist.insert(NODE_NETWORK | NODE_BLOOM); | |||||
filter_whitelist.insert(NODE_NETWORK | NODE_XTHIN); | |||||
filter_whitelist.insert(NODE_NETWORK | NODE_BLOOM | NODE_XTHIN); | |||||
} | |||||
if (host != nullptr && ns == nullptr) showHelp = true; | |||||
if (showHelp) fprintf(stderr, help, argv[0]); | |||||
} | |||||
}; | |||||
extern "C" { | extern "C" { | ||||
#include <seeder/dns.h> | #include <seeder/dns.h> | ||||
} | } | ||||
CAddrDb db; | CAddrDb db; | ||||
extern "C" void *ThreadCrawler(void *data) { | extern "C" void *ThreadCrawler(void *data) { | ||||
int *nThreads = (int *)data; | int *nThreads = (int *)data; | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | void cacheHit(uint64_t requestedFlags, bool force = false) { | ||||
thisflag.nIPv6++; | thisflag.nIPv6++; | ||||
} | } | ||||
} | } | ||||
thisflag.cacheHits = 0; | thisflag.cacheHits = 0; | ||||
thisflag.cacheTime = now; | thisflag.cacheTime = now; | ||||
} | } | ||||
} | } | ||||
CDnsThread(CDnsSeedOpts *opts, int idIn) : id(idIn) { | CDnsThread(std::set<uint64_t> &filter_whitelist, int idIn) : id(idIn) { | ||||
dns_opt.host = opts->host; | dns_opt.host = gArgs.GetArg("-host", "").c_str(); | ||||
dns_opt.ns = opts->ns; | dns_opt.ns = gArgs.GetArg("-ns", "").c_str(); | ||||
dns_opt.mbox = opts->mbox; | dns_opt.mbox = gArgs.GetArg("-mbox", "").c_str(); | ||||
dns_opt.datattl = 3600; | dns_opt.datattl = 3600; | ||||
dns_opt.nsttl = 40000; | dns_opt.nsttl = 40000; | ||||
dns_opt.cb = GetIPList; | dns_opt.cb = GetIPList; | ||||
dns_opt.port = opts->nPort; | dns_opt.port = gArgs.GetArg("-port", 53); | ||||
dns_opt.nRequests = 0; | dns_opt.nRequests = 0; | ||||
dbQueries = 0; | dbQueries = 0; | ||||
perflag.clear(); | perflag.clear(); | ||||
filterWhitelist = opts->filter_whitelist; | filterWhitelist = filter_whitelist; | ||||
} | } | ||||
void run() { dnsserver(&dns_opt); } | void run() { dnsserver(&dns_opt); } | ||||
}; | }; | ||||
extern "C" uint32_t GetIPList(void *data, char *requestedHostname, addr_t *addr, | extern "C" uint32_t GetIPList(void *data, char *requestedHostname, addr_t *addr, | ||||
uint32_t max, uint32_t ipv4, uint32_t ipv6) { | uint32_t max, uint32_t ipv4, uint32_t ipv6) { | ||||
CDnsThread *thread = (CDnsThread *)data; | CDnsThread *thread = (CDnsThread *)data; | ||||
▲ Show 20 Lines • Show All 174 Lines • ▼ Show 20 Lines | do { | ||||
true); | true); | ||||
} | } | ||||
} | } | ||||
Sleep(1800000); | Sleep(1800000); | ||||
} while (1); | } while (1); | ||||
return nullptr; | return nullptr; | ||||
} | } | ||||
int main(int argc, char **argv) { | static void SetupSeederArgs() { | ||||
// The logger dump everything on the console by default. | gArgs.AddArg("-?", _("Print this help message and exit"), false, | ||||
GetLogger().m_print_to_console = true; | OptionsCategory::OPTIONS); | ||||
gArgs.AddArg("-host=<host>", _("Hostname of the DNS seed"), false, | |||||
signal(SIGPIPE, SIG_IGN); | OptionsCategory::OPTIONS); | ||||
setbuf(stdout, nullptr); | gArgs.AddArg("-ns=<ns>", _("Hostname of the nameserver"), false, | ||||
CDnsSeedOpts opts; | OptionsCategory::OPTIONS); | ||||
opts.ParseCommandLine(argc, argv); | gArgs.AddArg("-mbox=<mbox>", _("E-Mail address reported in SOA records"), | ||||
false, OptionsCategory::OPTIONS); | |||||
gArgs.AddArg("-threads=<threads>", | |||||
_("Number of crawlers to run in parallel (default 96)"), false, | |||||
OptionsCategory::OPTIONS); | |||||
gArgs.AddArg("-dnsthreads=<threads>", | |||||
_("Number of DNS server threads (default 4)"), false, | |||||
OptionsCategory::OPTIONS); | |||||
gArgs.AddArg("-port=<port>", _("UDP port to listen on (default 53)"), false, | |||||
OptionsCategory::CONNECTION); | |||||
gArgs.AddArg("-onion=<ip:port>", _("Tor proxy IP/Port"), false, | |||||
OptionsCategory::CONNECTION); | |||||
gArgs.AddArg("-proxyipv4=<ip:port>", _("IPV4 SOCKS5 proxy IP/Port"), false, | |||||
OptionsCategory::CONNECTION); | |||||
gArgs.AddArg("-proxyipv6=<ip:port>", _("IPV6 SOCKS5 proxy IP/Port"), false, | |||||
OptionsCategory::CONNECTION); | |||||
gArgs.AddArg("-filter=<f1,f2,...>", | |||||
_("Allow these flag combinations as filters"), false, | |||||
OptionsCategory::OPTIONS); | |||||
gArgs.AddArg("-testnet", _("Use testnet"), false, | |||||
OptionsCategory::CHAINPARAMS); | |||||
gArgs.AddArg("-wipeban", _("Wipe list of banned nodes"), false, | |||||
OptionsCategory::CONNECTION); | |||||
gArgs.AddArg("-wipeignore", _("Wipe list of ignored nodes"), false, | |||||
OptionsCategory::CONNECTION); | |||||
gArgs.AddArg("-help", "", false, OptionsCategory::HIDDEN); | |||||
gArgs.AddArg("-h", "", false, OptionsCategory::HIDDEN); | |||||
} | |||||
static bool ProcessOptions(int argc, char *argv[], | |||||
std::set<uint64_t> &filter_whitelist) { | |||||
std::string error; | |||||
if (!gArgs.ParseParameters(argc, argv, error)) { | |||||
fprintf(stderr, "Error parsing command line arguments: %s\n", | |||||
error.c_str()); | |||||
return false; | |||||
} | |||||
if (HelpRequested(gArgs)) { | |||||
std::string strUsage = "Bitcoin-cash-seeder\n"; | |||||
strUsage += "Usage: bitcoin-seeder -host=<host> -ns=<ns> " | |||||
"[-mbox=<mbox>] [-threads=<threads>] [-port=<port>]\n"; | |||||
strUsage += "\n" + gArgs.GetHelpMessage(); | |||||
fprintf(stdout, "%s", strUsage.c_str()); | |||||
return true; | |||||
} | |||||
// Get whitelist filter flags | |||||
if (gArgs.IsArgSet("-filter")) { | |||||
// Parse whitelist additions | |||||
auto flags = gArgs.GetArg("-filter", ""); | |||||
size_t flagstartpos = 0; | |||||
while (flagstartpos < flags.size() && | |||||
flagstartpos != std::string::npos) { | |||||
size_t flagendpos = flags.find_first_of(",", flagstartpos); | |||||
uint64_t flag = | |||||
atoi64(flags.substr(flagstartpos, (flagendpos - flagstartpos))); | |||||
filter_whitelist.insert(flag); | |||||
if (flagendpos == std::string::npos) { | |||||
break; | |||||
} | |||||
flagstartpos = flagendpos + 1; | |||||
} | |||||
} | |||||
if (filter_whitelist.empty()) { | |||||
filter_whitelist.insert(NODE_NETWORK); | |||||
filter_whitelist.insert(NODE_NETWORK | NODE_BLOOM); | |||||
filter_whitelist.insert(NODE_NETWORK | NODE_XTHIN); | |||||
filter_whitelist.insert(NODE_NETWORK | NODE_BLOOM | NODE_XTHIN); | |||||
} | |||||
fprintf(stdout, "Supporting whitelisted filters: "); | fprintf(stdout, "Supporting whitelisted filters: "); | ||||
for (std::set<uint64_t>::const_iterator it = opts.filter_whitelist.begin(); | for (std::set<uint64_t>::const_iterator it = filter_whitelist.begin(); | ||||
it != opts.filter_whitelist.end(); it++) { | it != filter_whitelist.end(); it++) { | ||||
if (it != opts.filter_whitelist.begin()) { | if (it != filter_whitelist.begin()) { | ||||
fprintf(stdout, ","); | fprintf(stdout, ","); | ||||
} | } | ||||
fprintf(stdout, "0x%lx", (unsigned long)*it); | fprintf(stdout, "0x%lx", (unsigned long)*it); | ||||
} | } | ||||
fprintf(stdout, "\n"); | fprintf(stdout, "\n"); | ||||
if (opts.tor) { | // Check tor proxy | ||||
CService service(LookupNumeric(opts.tor, 9050)); | if (!gArgs.GetArg("-onion", "").empty()) { | ||||
CService service( | |||||
LookupNumeric(gArgs.GetArg("-onion", "").c_str(), 9050)); | |||||
if (service.IsValid()) { | if (service.IsValid()) { | ||||
fprintf(stdout, "Using Tor proxy at %s\n", | fprintf(stdout, "Using Tor proxy at %s\n", | ||||
service.ToStringIPPort().c_str()); | service.ToStringIPPort().c_str()); | ||||
SetProxy(NET_ONION, proxyType(service)); | SetProxy(NET_ONION, proxyType(service)); | ||||
} | } | ||||
} | } | ||||
if (opts.ipv4_proxy) { | // Check ipv4 proxy | ||||
CService service(LookupNumeric(opts.ipv4_proxy, 9050)); | if (!gArgs.GetArg("-proxyipv4", "").empty()) { | ||||
CService service( | |||||
LookupNumeric(gArgs.GetArg("-proxyipv4", "").c_str(), 9050)); | |||||
if (service.IsValid()) { | if (service.IsValid()) { | ||||
fprintf(stdout, "Using IPv4 proxy at %s\n", | fprintf(stdout, "Using IPv4 proxy at %s\n", | ||||
service.ToStringIPPort().c_str()); | service.ToStringIPPort().c_str()); | ||||
SetProxy(NET_IPV4, proxyType(service)); | SetProxy(NET_IPV4, proxyType(service)); | ||||
} | } | ||||
} | } | ||||
if (opts.ipv6_proxy) { | // Check ipv6 proxy | ||||
CService service(LookupNumeric(opts.ipv6_proxy, 9050)); | if (!gArgs.GetArg("-proxyipv6", "").empty()) { | ||||
CService service( | |||||
LookupNumeric(gArgs.GetArg("-proxyipv6", "").c_str(), 9050)); | |||||
if (service.IsValid()) { | if (service.IsValid()) { | ||||
fprintf(stdout, "Using IPv6 proxy at %s\n", | fprintf(stdout, "Using IPv6 proxy at %s\n", | ||||
service.ToStringIPPort().c_str()); | service.ToStringIPPort().c_str()); | ||||
SetProxy(NET_IPV6, proxyType(service)); | SetProxy(NET_IPV6, proxyType(service)); | ||||
} | } | ||||
} | } | ||||
bool fDNS = true; | |||||
if (opts.fUseTestNet) { | // Get -testnet option | ||||
if (gArgs.GetBoolArg("-testnet", false)) { | |||||
fprintf(stdout, "Using testnet.\n"); | fprintf(stdout, "Using testnet.\n"); | ||||
netMagic[0] = 0xf4; | netMagic[0] = 0xf4; | ||||
netMagic[1] = 0xe5; | netMagic[1] = 0xe5; | ||||
netMagic[2] = 0xf3; | netMagic[2] = 0xf3; | ||||
netMagic[3] = 0xf4; | netMagic[3] = 0xf4; | ||||
seeds = testnet_seeds; | seeds = testnet_seeds; | ||||
fTestNet = true; | |||||
} | } | ||||
if (!opts.ns) { | // Check hostname of nameserver | ||||
if (gArgs.GetArg("-ns", "").empty()) { | |||||
// No hostname set | |||||
fprintf(stdout, "No nameserver set. Not starting DNS server.\n"); | fprintf(stdout, "No nameserver set. Not starting DNS server.\n"); | ||||
fDNS = false; | |||||
} | } | ||||
if (fDNS && !opts.host) { | |||||
fprintf(stderr, "No hostname set. Please use -h.\n"); | // Check hostname of DNS seed | ||||
exit(1); | if (!gArgs.GetArg("-ns", "").empty() && gArgs.GetArg("-host", "").empty()) { | ||||
} | fprintf(stderr, "No hostname set. Please use -host.\n"); | ||||
if (fDNS && !opts.mbox) { | return false; | ||||
fprintf(stderr, "No e-mail address set. Please use -m.\n"); | |||||
exit(1); | |||||
} | } | ||||
// Check email | |||||
if (!gArgs.GetArg("-ns", "").empty() && gArgs.GetArg("-mbox", "").empty()) { | |||||
fprintf(stderr, "No e-mail address set. Please use -mbox.\n"); | |||||
return false; | |||||
} | |||||
FILE *f = fsbridge::fopen("dnsseed.dat", "r"); | FILE *f = fsbridge::fopen("dnsseed.dat", "r"); | ||||
if (f) { | if (f) { | ||||
fprintf(stdout, "Loading dnsseed.dat..."); | fprintf(stdout, "Loading dnsseed.dat..."); | ||||
CAutoFile cf(f, SER_DISK, CLIENT_VERSION); | CAutoFile cf(f, SER_DISK, CLIENT_VERSION); | ||||
cf >> db; | cf >> db; | ||||
if (opts.fWipeBan) db.banned.clear(); | // Check for -wipeban and the -wipeignore | ||||
if (opts.fWipeIgnore) db.ResetIgnores(); | if (gArgs.GetBoolArg("-wipeban", false)) { | ||||
db.banned.clear(); | |||||
fprintf(stdout, "Ban list wiped..."); | |||||
} | |||||
if (gArgs.GetBoolArg("-wipeignore", false)) { | |||||
db.ResetIgnores(); | |||||
fprintf(stdout, "Ignore list wiped..."); | |||||
} | |||||
fprintf(stdout, "done\n"); | fprintf(stdout, "done\n"); | ||||
} | } | ||||
// Get DNS threading and port options | |||||
if (gArgs.GetArg("-dnsthreads", 4) < 1 && | |||||
gArgs.GetArg("-dnsthreads", 4) > 999) { | |||||
gArgs.ForceSetArg("-dnsthreads", "4"); | |||||
} | |||||
if (gArgs.GetArg("-port", 53) < 1 && gArgs.GetArg("-port", 53) > 65535) { | |||||
gArgs.ForceSetArg("-port", "53"); | |||||
} | |||||
// Get threading options | |||||
if (gArgs.GetArg("-threads", 96) < 1 && | |||||
gArgs.GetArg("-threads", 96) > 999) { | |||||
gArgs.ForceSetArg("-threads", "96"); | |||||
} | |||||
return true; | |||||
} | |||||
static bool AppInit(int argc, char *argv[]) { | |||||
// The logger dump everything on the console by default. | |||||
GetLogger().m_print_to_console = true; | |||||
signal(SIGPIPE, SIG_IGN); | |||||
setbuf(stdout, nullptr); | |||||
SetupSeederArgs(); | |||||
std::set<uint64_t> filter_whitelist; | |||||
if (!ProcessOptions(argc, argv, filter_whitelist)) { | |||||
return false; | |||||
} | |||||
pthread_t threadDns, threadSeed, threadDump, threadStats; | pthread_t threadDns, threadSeed, threadDump, threadStats; | ||||
if (fDNS) { | if (!gArgs.GetArg("-ns", "").empty()) { | ||||
fprintf(stdout, "Starting %i DNS threads for %s on %s (port %i)...", | fprintf(stdout, "Starting %li DNS threads for %s on %s (port %li)...", | ||||
opts.nDnsThreads, opts.host, opts.ns, opts.nPort); | gArgs.GetArg("-dnsthreads", 4), | ||||
gArgs.GetArg("-host", "").c_str(), | |||||
gArgs.GetArg("-ns", "").c_str(), gArgs.GetArg("-port", 53)); | |||||
dnsThread.clear(); | dnsThread.clear(); | ||||
for (int i = 0; i < opts.nDnsThreads; i++) { | for (int i = 0; i < gArgs.GetArg("-dnsthreads", 4); i++) { | ||||
dnsThread.push_back(new CDnsThread(&opts, i)); | dnsThread.push_back(new CDnsThread(filter_whitelist, i)); | ||||
pthread_create(&threadDns, nullptr, ThreadDNS, dnsThread[i]); | pthread_create(&threadDns, nullptr, ThreadDNS, dnsThread[i]); | ||||
fprintf(stdout, "."); | fprintf(stdout, "."); | ||||
Sleep(20); | Sleep(20); | ||||
} | } | ||||
fprintf(stdout, "done\n"); | fprintf(stdout, "done\n"); | ||||
} | } | ||||
uint64_t threads = gArgs.GetArg("-threads", 96); | |||||
fprintf(stdout, "Starting seeder..."); | fprintf(stdout, "Starting seeder..."); | ||||
pthread_create(&threadSeed, nullptr, ThreadSeeder, nullptr); | pthread_create(&threadSeed, nullptr, ThreadSeeder, nullptr); | ||||
fprintf(stdout, "done\n"); | fprintf(stdout, "done\n"); | ||||
fprintf(stdout, "Starting %i crawler threads...", opts.nThreads); | fprintf(stdout, "Starting %li crawler threads...", threads); | ||||
pthread_attr_t attr_crawler; | pthread_attr_t attr_crawler; | ||||
pthread_attr_init(&attr_crawler); | pthread_attr_init(&attr_crawler); | ||||
pthread_attr_setstacksize(&attr_crawler, 0x20000); | pthread_attr_setstacksize(&attr_crawler, 0x20000); | ||||
for (int i = 0; i < opts.nThreads; i++) { | for (size_t i = 0; i < threads; i++) { | ||||
pthread_t thread; | pthread_t thread; | ||||
pthread_create(&thread, &attr_crawler, ThreadCrawler, &opts.nThreads); | pthread_create(&thread, &attr_crawler, ThreadCrawler, &threads); | ||||
} | } | ||||
pthread_attr_destroy(&attr_crawler); | pthread_attr_destroy(&attr_crawler); | ||||
fprintf(stdout, "done\n"); | fprintf(stdout, "done\n"); | ||||
pthread_create(&threadStats, nullptr, ThreadStats, nullptr); | pthread_create(&threadStats, nullptr, ThreadStats, nullptr); | ||||
pthread_create(&threadDump, nullptr, ThreadDumper, nullptr); | pthread_create(&threadDump, nullptr, ThreadDumper, nullptr); | ||||
void *res; | void *res; | ||||
pthread_join(threadDump, &res); | pthread_join(threadDump, &res); | ||||
return 0; | return 0; | ||||
} | } | ||||
int main(int argc, char **argv) { | |||||
return AppInit(argc, argv); | |||||
} |