Commit d739a765 authored by Bernd Zeimetz's avatar Bernd Zeimetz

Merge branch 'master' into jessie-backports

parents 5f43e1af 93a3956b
# 2.3 (unreleased master branch)
# 2.4 (unreleased master branch)
# 2.3 (2016-11-07)
### Bugfixes
* [Issue #213](https://github.com/grobian/carbon-c-relay/issues/213)
Change to aggregates to not cause HUP to reload when more than 10
aggregates are defined.
* [Issue #214](https://github.com/grobian/carbon-c-relay/issues/214)
`-U` option doesn't set UDP receive buffer size.
* [Issue #218](https://github.com/grobian/carbon-c-relay/issues/218)
zeros inserted after some metrics.
* [Issue #219](https://github.com/grobian/carbon-c-relay/issues/219)
should fail if port is unavailable
* [Issue #224](https://github.com/grobian/carbon-c-relay/issues/224)
segfault during SIGHUP
# 2.2 (2016-09-11)
......
......@@ -84,7 +84,7 @@ fnv1a_hashpos(const char *key, const char *end)
* Computes the bucket number for key in the range [0, bckcnt). The
* algorithm used is the jump consistent hash by Lamping and Veach.
*/
static int
static unsigned int
jump_bucketpos(unsigned long long int key, int bckcnt)
{
long long int b = -1, j = 0;
......@@ -98,7 +98,7 @@ jump_bucketpos(unsigned long long int key, int bckcnt)
}
/* b cannot exceed the range of bckcnt, see while condition */
return (int)b;
return (unsigned int)b;
}
/**
......
carbon-c-relay (2.2-1~bpo8+1) jessie-backports; urgency=medium
carbon-c-relay (2.3-1~bpo8+1) jessie-backports; urgency=medium
* Rebuild for jessie-backports.
-- Bernd Zeimetz <bzed@debian.org> Sat, 05 Nov 2016 00:37:50 +0100
-- Bernd Zeimetz <bzed@debian.org> Fri, 02 Dec 2016 10:00:34 +0100
carbon-c-relay (2.3-1) unstable; urgency=medium
* [b994cee] Merge tag 'upstream/2.3'
Upstream version 2.3
-- Bernd Zeimetz <bzed@debian.org> Tue, 22 Nov 2016 21:52:53 +0100
carbon-c-relay (2.2-1) unstable; urgency=medium
......
......@@ -123,32 +123,33 @@ dispatch_addlistener(int sock)
connection *newconn;
int c;
newconn = malloc(sizeof(connection));
if (newconn == NULL)
return 1;
(void) fcntl(sock, F_SETFL, O_NONBLOCK);
newconn->sock = sock;
newconn->takenby = 0;
newconn->buflen = 0;
pthread_rwlock_wrlock(&listenerslock);
if (listeners == NULL) {
listeners = malloc(sizeof(connection *) * MAX_LISTENERS);
if ((listeners = malloc(sizeof(connection *) * MAX_LISTENERS)) == NULL)
{
pthread_rwlock_unlock(&listenerslock);
return 1;
}
for (c = 0; c < MAX_LISTENERS; c++)
listeners[c] = NULL;
}
if (listeners == NULL) {
pthread_rwlock_unlock(&listenerslock);
return 1;
}
for (c = 0; c < MAX_LISTENERS; c++) {
if (listeners[c] == NULL) {
newconn = malloc(sizeof(connection));
if (newconn == NULL) {
pthread_rwlock_unlock(&listenerslock);
return 1;
}
(void) fcntl(sock, F_SETFL, O_NONBLOCK);
newconn->sock = sock;
newconn->takenby = 0;
newconn->buflen = 0;
listeners[c] = newconn;
break;
}
}
if (c == MAX_LISTENERS) {
free(newconn);
logerr("cannot add new listener: "
"no more free listener slots (max = %zu)\n",
MAX_LISTENERS);
......@@ -333,6 +334,10 @@ dispatch_process_dests(connection *conn, dispatcher *self, struct timeval now)
/* force after timeout */
force = timediff(conn->lastwork, now) > conn->maxsenddelay;
for (i = 0; i < conn->destlen; i++) {
tracef("dispatcher %d, connfd %d, metric %s, queueing to %s:%d\n",
self->id, conn->sock, conn->dests[i].metric,
server_ip(conn->dests[i].dest),
server_port(conn->dests[i].dest));
if (server_send(conn->dests[i].dest, conn->dests[i].metric, force) == 0)
break;
}
......@@ -403,8 +408,11 @@ dispatch_connection(connection *conn, dispatcher *self, struct timeval start)
(sizeof(conn->buf) - 1) - conn->buflen)) > 0
)
{
if (len > 0)
if (len > 0) {
conn->buflen += len;
tracef("dispatcher %d, connfd %d, read %d bytes from socket\n",
self->id, conn->sock, len);
}
/* metrics look like this: metric_path value timestamp\n
* due to various messups we need to sanitise the
......@@ -431,11 +439,15 @@ dispatch_connection(connection *conn, dispatcher *self, struct timeval start)
*q = '\0'; /* can do this because we substract one from buf */
/* perform routing of this metric */
tracef("dispatcher %d, connfd %d, metric %s",
self->id, conn->sock, conn->metric);
__sync_add_and_fetch(&(self->blackholes),
router_route(self->rtr,
conn->dests, &conn->destlen, CONN_DESTS_SIZE,
conn->srcaddr,
conn->metric, firstspace));
tracef("dispatcher %d, connfd %d, destinations %zd\n",
self->id, conn->sock, conn->destlen);
/* restart building new one from the start */
q = conn->metric;
......@@ -671,6 +683,12 @@ dispatch_new(char id, enum conntype type, router *r, char *allowed_chars)
return ret;
}
void
dispatch_set_bufsize(unsigned int nsockbufsize)
{
sockbufsize = nsockbufsize;
}
static char globalid = 0;
/**
......@@ -678,10 +696,9 @@ static char globalid = 0;
* (and putting them on the queue for handling the connections).
*/
dispatcher *
dispatch_new_listener(unsigned int nsockbufsize)
dispatch_new_listener(void)
{
char id = globalid++;
sockbufsize = nsockbufsize;
return dispatch_new(id, LISTENER, NULL, NULL);
}
......
......@@ -30,7 +30,8 @@ int dispatch_addlistener_udp(int sock);
void dispatch_removelistener(int sock);
int dispatch_addconnection(int sock);
int dispatch_addconnection_aggr(int sock);
dispatcher *dispatch_new_listener(unsigned int sockbufsize);
void dispatch_set_bufsize(unsigned int sockbufsize);
dispatcher *dispatch_new_listener(void);
dispatcher *dispatch_new_connection(router *r, char *allowed_chars);
void dispatch_stop(dispatcher *d);
void dispatch_shutdown(dispatcher *d);
......
cluster out file /tmp/issue-211;
aggregate
^carbon\.relays\.[^.]+\.metricsReceived$
every 60 seconds
expire after 70 seconds
compute sum write to
collectd.sum.if_octets.total
stop;
match
^carbon\.relays\.
send to blackhole
stop;
match * send to out;
cluster graphite forward 127.0.0.1:2004;
aggregate
^1\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^2\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^3\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^4\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^5\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^6\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^7\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^8\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^9\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^10\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
aggregate
^11\.(foo|bar)\.some_instance\.([^.]+)\.(.+)
every 30 seconds
expire after 90 seconds
compute sum write to
sums.some_instance.\2.\3
send to graphite
;
rewrite abcdefg0 into b;
rewrite abcdefg1 into b;
rewrite abcdefg2 into b;
rewrite abcdefg3 into b;
rewrite abcdefg4 into b;
rewrite abcdefg5 into b;
rewrite abcdefg6 into b;
rewrite abcdefg7 into b;
rewrite abcdefg8 into b;
rewrite abcdefg9 into b;
rewrite abcdefg10 into b;
rewrite abcdefg11 into b;
rewrite abcdefg12 into b;
rewrite abcdefg13 into b;
rewrite abcdefg14 into b;
rewrite abcdefg15 into b;
rewrite abcdefg16 into b;
rewrite abcdefg17 into b;
rewrite abcdefg18 into b;
rewrite abcdefg19 into b;
rewrite abcdefg20 into b;
rewrite abcdefg21 into b;
rewrite abcdefg22 into b;
rewrite abcdefg23 into b;
rewrite abcdefg24 into b;
rewrite abcdefg25 into b;
rewrite abcdefg26 into b;
rewrite abcdefg27 into b;
rewrite abcdefg28 into b;
rewrite abcdefg29 into b;
rewrite abcdefg30 into b;
rewrite abcdefg31 into b;
rewrite abcdefg32 into b;
rewrite abcdefg33 into b;
rewrite abcdefg34 into b;
rewrite abcdefg35 into b;
rewrite abcdefg36 into b;
rewrite abcdefg37 into b;
rewrite abcdefg38 into b;
rewrite abcdefg39 into b;
rewrite abcdefg40 into b;
rewrite abcdefg41 into b;
rewrite abcdefg42 into b;
rewrite abcdefg43 into b;
rewrite abcdefg44 into b;
rewrite abcdefg45 into b;
rewrite abcdefg46 into b;
rewrite abcdefg47 into b;
rewrite abcdefg48 into b;
rewrite abcdefg49 into b;
rewrite abcdefg50 into b;
rewrite abcdefg51 into b;
rewrite abcdefg52 into b;
rewrite abcdefg53 into b;
rewrite abcdefg54 into b;
rewrite abcdefg55 into b;
rewrite abcdefg56 into b;
rewrite abcdefg57 into b;
rewrite abcdefg58 into b;
rewrite abcdefg59 into b;
......@@ -56,6 +56,7 @@ bindlisten(
char buf[128];
char saddr[INET6_ADDRSTRLEN];
int err;
int binderr = 0;
int curlen_stream = 0;
int curlen_dgram = 0;
int socktypes[] = {SOCK_STREAM, SOCK_DGRAM, 0};
......@@ -64,7 +65,7 @@ bindlisten(
tv.tv_sec = 0;
tv.tv_usec = 500 * 1000;
for (; *socktype != 0; socktype++) {
for (; *socktype != 0 && binderr == 0; socktype++) {
memset(&hint, 0, sizeof(hint));
hint.ai_family = PF_UNSPEC;
hint.ai_socktype = *socktype;
......@@ -84,8 +85,14 @@ bindlisten(
continue;
if (resw->ai_protocol != IPPROTO_TCP && resw->ai_protocol != IPPROTO_UDP)
continue;
if ((sock = socket(resw->ai_family, resw->ai_socktype, resw->ai_protocol)) < 0)
continue;
if ((sock = socket(resw->ai_family, resw->ai_socktype, resw->ai_protocol)) < 0) {
while (curlen_dgram > 0)
close(ret_dgram[--curlen_dgram]);
while (curlen_stream > 0)
close(ret_stream[--curlen_stream]);
binderr = 1;
break;
}
(void) setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
optval = 1; /* allow takeover */
......@@ -95,11 +102,6 @@ bindlisten(
(void) setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &optval, sizeof(optval));
}
if (bind(sock, resw->ai_addr, resw->ai_addrlen) < 0) {
close(sock);
continue;
}
snprintf(saddr, sizeof(saddr), "(unknown)");
switch (resw->ai_family) {
case PF_INET:
......@@ -114,10 +116,28 @@ bindlisten(
break;
}
if (bind(sock, resw->ai_addr, resw->ai_addrlen) < 0) {
logerr("failed to bind on %s%d %s port %s\n",
resw->ai_protocol == IPPROTO_TCP ? "tcp" : "udp",
resw->ai_family == PF_INET6 ? 6 : 4, saddr, buf);
close(sock);
while (curlen_dgram > 0)
close(ret_dgram[--curlen_dgram]);
while (curlen_stream > 0)
close(ret_stream[--curlen_stream]);
binderr = 1;
break;
}
if (resw->ai_protocol == IPPROTO_TCP) {
if (listen(sock, backlog) < 0) {
close(sock);
continue;
while (curlen_dgram > 0)
close(ret_dgram[--curlen_dgram]);
while (curlen_stream > 0)
close(ret_stream[--curlen_stream]);
binderr = 1;
break;
}
if (curlen_stream < *retlen_stream) {
logout("listening on tcp%d %s port %s\n",
......
......@@ -76,6 +76,10 @@ relaylog(enum logdst dest, const char *fmt, ...)
char console = 0;
int ret;
/* briefly stall if we're swapping fds */
while (!__sync_add_and_fetch(&relay_can_log, 0))
usleep((100 + (rand() % 200)) * 1000); /* 100ms - 300ms */
switch (dest) {
case LOGOUT:
dst = relay_stdout;
......@@ -90,10 +94,6 @@ relaylog(enum logdst dest, const char *fmt, ...)
}
assert(dst != NULL);
/* briefly stall if we're swapping fds */
while (!__sync_add_and_fetch(&relay_can_log, 0))
usleep((100 + (rand() % 200)) * 1000); /* 100ms - 300ms */
time(&now);
localtime_r(&now, &tm_now);
len = strftime(prefix, sizeof(prefix), "[%Y-%m-%d %H:%M:%S]", &tm_now);
......@@ -357,7 +357,14 @@ main(int argc, char * const argv[])
do_version();
break;
case 'd':
mode |= MODE_DEBUG;
/* secret support for -dd (just trace) and -ddd (debug
* and trace) */
if (mode & MODE_DEBUG) {
mode |= MODE_TRACE;
mode &= ~MODE_DEBUG;
} else {
mode |= MODE_DEBUG;
}
break;
case 'm':
smode = SUB;
......@@ -661,6 +668,10 @@ main(int argc, char * const argv[])
allowed_chars);
if (mode & MODE_DEBUG)
fprintf(relay_stdout, " debug = true\n");
#ifdef ENABLE_TRACE
if (mode & MODE_TRACE)
fprintf(relay_stdout, " trace = true\n");
#endif
else if (mode & MODE_SUBMISSION)
fprintf(relay_stdout, " submission = true\n");
else if (mode & MODE_DAEMON)
......@@ -679,16 +690,16 @@ main(int argc, char * const argv[])
aggrs = router_getaggregators(rtr);
numaggregators = aggregator_numaggregators(aggrs);
if (numaggregators > 10 && mode & MODE_DEBUG) {
if (numaggregators > 10 && !(mode & MODE_DEBUG)) {
fprintf(relay_stdout, "parsed configuration follows:\n"
"(%zu aggregations with %zu computations omitted "
"for brevity)\n",
numaggregators, aggregator_numcomputes(aggrs));
router_printconfig(rtr, relay_stdout, 0);
router_printconfig(rtr, relay_stdout, PMODE_NORM);
} else {
fprintf(relay_stdout, "parsed configuration follows:\n");
router_printconfig(rtr, relay_stdout,
1 + (mode & MODE_DEBUG ? 2 : 0));
PMODE_AGGR + (mode & MODE_DEBUG ? PMODE_DEBUG : PMODE_NORM));
}
fprintf(relay_stdout, "\n");
......@@ -742,6 +753,7 @@ main(int argc, char * const argv[])
listenport, strerror(errno));
return -1;
}
dispatch_set_bufsize(sockbufsize);
for (ch = 0; ch < stream_socklen; ch++) {
if (dispatch_addlistener(stream_sock[ch]) != 0) {
logerr("failed to add listener\n");
......@@ -754,7 +766,7 @@ main(int argc, char * const argv[])
return -1;
}
}
if ((workers[0] = dispatch_new_listener(sockbufsize)) == NULL)
if ((workers[0] = dispatch_new_listener()) == NULL)
logerr("failed to add listener\n");
if (allowed_chars == NULL)
......
......@@ -18,7 +18,7 @@
#ifndef HAVE_RELAY_H
#define HAVE_RELAY_H 1
#define VERSION "2.2"
#define VERSION "2.3"
#define METRIC_BUFSIZ 8192
......@@ -27,8 +27,15 @@
#define MODE_SUBMISSION (1 << 1)
#define MODE_TEST (1 << 2)
#define MODE_DAEMON (1 << 3)
#define MODE_TRACE (1 << 4)
extern unsigned char mode;
#ifdef ENABLE_TRACE
#define tracef(...) if (mode & MODE_TRACE) fprintf(stdout, __VA_ARGS__)
#else
#define tracef(...) /* noop */
#endif
typedef enum { CON_TCP, CON_UDP, CON_PIPE, CON_FILE } serv_ctype;
extern char relay_hostname[];
......
......@@ -162,6 +162,7 @@ ra_malloc(router *rtr, size_t sz)
size_t nsz;
#define ra_alloc(RA, SZ) { \
assert(SZ >= 0); \
nsz = 256 * 1024; \
if (SZ > nsz) \
nsz = ((SZ / 1024) + 1) * 1024; \
......@@ -411,6 +412,7 @@ router_readconfig(router *orig,
ret->routes = NULL;
ret->aggregators = NULL;
ret->srvrs = NULL;
ret->clusters = NULL;
/* create virtual blackhole cluster */
cl = ra_malloc(ret, sizeof(cluster));
......@@ -1972,8 +1974,8 @@ router_optimise(router *r)
blocks->next = NULL;
rlast = NULL;
for (rwalk = r->routes; rwalk != NULL; rwalk = rnext) {
/* matchall rules cannot be in a group */
if (rwalk->matchtype == MATCHALL) {
/* matchall and rewrite rules cannot be in a group (issue #218) */
if (rwalk->matchtype == MATCHALL || rwalk->dests->cl->type == REWRITE) {
blast->next = malloc(sizeof(block));
blast->next->prev = blast;
blast = blast->next;
......@@ -2041,9 +2043,9 @@ router_optimise(router *r)
while (
p >= rwalk->pattern && b - pblock < sizeof(pblock) &&
(
(*p >= 'a' && *p <= 'z') ||
(*p >= 'A' && *p <= 'Z') ||
*p == '_'
(*p >= 'a' && *p <= 'z') ||
(*p >= 'A' && *p <= 'Z') ||
*p == '_'
)
)
{
......@@ -2290,7 +2292,7 @@ router_printconfig(router *rtr, FILE *f, char pmode)
PPROTO);
}
fprintf(f, " ;\n");
if (pmode & 2) {
if (pmode & PMODE_HASH) {
if (c->type == CARBON_CH ||
c->type == FNV1A_CH ||
c->type == JUMP_CH)
......@@ -2308,10 +2310,10 @@ router_printconfig(router *rtr, FILE *f, char pmode)
char stubname[48];
char percentile[16];
if (!(pmode & 1))
if (!(pmode & PMODE_AGGR))
continue;
if (pmode & 2 || r->dests->next == NULL) {
if (pmode & PMODE_STUB || r->dests->next == NULL) {
stubname[0] = '\0';
} else {
snprintf(stubname, sizeof(stubname),
......@@ -2352,7 +2354,7 @@ router_printconfig(router *rtr, FILE *f, char pmode)
"<unknown>",
ac->metric + strlen(stubname));
}
if (!(pmode & 2) && r->dests->next != NULL) {
if (!(pmode & PMODE_STUB) && r->dests->next != NULL) {
destinations *dn = r->dests->next;
fprintf(f, " send to");
if (dn->next == NULL) {
......@@ -2383,10 +2385,17 @@ router_printconfig(router *rtr, FILE *f, char pmode)
fprintf(f, "# common pattern group '%s' "
"contains %zu aggregations/matches\n",
++b, cnt);
if (mode & PMODE_AGGR) {
router srtr;
memset(&srtr, 0, sizeof(srtr));
srtr.routes = r->dests->cl->members.routes;
/* recurse */
router_printconfig(&srtr, f, pmode);
}
} else if (r->dests->cl->type == AGGRSTUB ||
r->dests->cl->type == STATSTUB)
{
if (pmode & 2) {
if (pmode & PMODE_STUB) {
fprintf(f, "# stub match for aggregate/statistics rule "
"with send to\n");
fprintf(f, "match ^%s\n send to", r->pattern);
......@@ -2454,6 +2463,7 @@ router_printdiffs(router *old, router *new, FILE *out)
char buf[1024];
size_t len;
int ret;
mode_t mask;
/* First idea was to printconfig both old and new, and run diff -u
* on them. Simple and effective. Does require diff and two files
......@@ -2463,7 +2473,9 @@ router_printdiffs(router *old, router *new, FILE *out)
tmp = "/tmp";
snprintf(patho, sizeof(patho), "%s/carbon-c-relay_route.XXXXXX", tmp);
mask = umask(S_IWGRP | S_IWOTH);
fd = mkstemp(patho);
umask(mask);
if (fd < 0) {
logerr("failed to create temporary file: %s\n", strerror(errno));
return 1;
......@@ -2474,11 +2486,13 @@ router_printdiffs(router *old, router *new, FILE *out)
patho, strerror(errno));
return 1;
}
router_printconfig(old, f, 1);
router_printconfig(old, f, PMODE_AGGR);
fclose(f);
snprintf(pathn, sizeof(pathn), "%s/carbon-c-relay_route.XXXXXX", tmp);
mask = umask(S_IWGRP | S_IWOTH);
fd = mkstemp(pathn);
umask(mask);
if (fd < 0) {
logerr("failed to create temporary file: %s\n", strerror(errno));
return 1;
......@@ -2489,14 +2503,16 @@ router_printdiffs(router *old, router *new, FILE *out)
pathn, strerror(errno));
return 1;
}
router_printconfig(new, f, 1);
router_printconfig(new, f, PMODE_AGGR);
fclose(f);
/* diff and print its output */
snprintf(buf, sizeof(buf), "diff -u %s %s", patho, pathn);
f = popen(buf, "r");
while ((len = fread(buf, 1, sizeof(buf) - 1, f)) > 0) {
fwrite(buf, len, 1, out);
if (fwrite(buf, len, 1, out) == 0)
/* ignore */
;
}
ret = pclose(f);
......
......@@ -24,6 +24,12 @@
#include "server.h"
#include "aggregator.h"
#define PMODE_NORM (1 << 0)
#define PMODE_AGGR (1 << 1)
#define PMODE_HASH (1 << 2)
#define PMODE_STUB (1 << 3)
#define PMODE_DEBUG (PMODE_HASH | PMODE_STUB)
#define CONN_DESTS_SIZE 64
typedef struct {
const char *metric;
......
......@@ -373,9 +373,10 @@ server_queuereader(void *d)
&self->sockbufsize, sizeof(self->sockbufsize)) != 0)
; /* ignore */
#ifdef SO_NOSIGPIPE
if (setsockopt(self->fd, SOL_SOCKET, SO_NOSIGPIPE, NULL, 0) != 0)
logout("warning: failed to ignore SIGPIPE on socket: %s\n",
strerror(errno));
if (self->ctype == CON_TCP || self->ctype == CON_UDP)
if (setsockopt(self->fd, SOL_SOCKET, SO_NOSIGPIPE, NULL, 0) != 0)
logout("warning: failed to ignore SIGPIPE on socket: %s\n",
strerror(errno));
#endif
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment