#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include "distcc.h"
#include "trace.h"
#include "util.h"
#include "hosts.h"
#include "exitcode.h"
#include "snprintf.h"
#ifdef HAVE_AVAHI
#include "zeroconf.h"
#define ZEROCONF_MAGIC "+zeroconf"
#endif
const int dcc_default_port = DISTCC_DEFAULT_PORT;
struct rand_container {
struct dcc_hostdef *host;
int rand;
};
int dcc_randomize_host_list(struct dcc_hostdef **host_list, int length);
int dcc_compare_container(const void *a, const void *b);
#ifdef HAVE_STRNDUP
#define my_strndup(src, size) strndup(src, size)
#else
static char *my_strndup(const char *src, size_t size)
{
char *dst;
dst = malloc(size + 1);
if (dst == NULL)
return NULL;
strncpy(dst, src, size);
dst[size] = '\0';
return dst;
}
#endif
int dcc_get_hostlist(struct dcc_hostdef **ret_list,
int *ret_nhosts)
{
char *env;
char *path, *top;
int ret;
*ret_list = NULL;
*ret_nhosts = 0;
if ((env = getenv("DISTCC_HOSTS")) != NULL) {
rs_trace("read hosts from environment");
return dcc_parse_hosts(env, "$DISTCC_HOSTS", ret_list, ret_nhosts, NULL);
}
if ((ret = dcc_get_top_dir(&top)) == 0) {
asprintf(&path, "%s/hosts", top);
if (access(path, R_OK) == 0) {
ret = dcc_parse_hosts_file(path, ret_list, ret_nhosts);
free(path);
return ret;
} else {
rs_trace("not reading %s: %s", path, strerror(errno));
free(path);
}
}
asprintf(&path, "%s/distcc/hosts", SYSCONFDIR);
if (access(path, R_OK) == 0) {
ret = dcc_parse_hosts_file(path, ret_list, ret_nhosts);
free(path);
return ret;
} else {
rs_trace("not reading %s: %s", path, strerror(errno));
free(path);
}
rs_log_warning("no hostlist is set; can't distribute work");
return EXIT_BAD_HOSTSPEC;
}
static int dcc_parse_multiplier(const char **psrc, struct dcc_hostdef *hostdef)
{
const char *token = *psrc;
if ((*psrc)[0] == '/' || (*psrc)[0] == '=') {
int val;
(*psrc)++;
val = atoi(*psrc);
if (val == 0) {
rs_log_error("bad multiplier \"%s\" in host specification", token);
return EXIT_BAD_HOSTSPEC;
}
while (isdigit(**psrc))
(*psrc)++;
hostdef->n_slots = val;
}
return 0;
}
static int dcc_parse_options(const char **psrc,
struct dcc_hostdef *host)
{
const char *started = *psrc, *p = *psrc;
host->compr = DCC_COMPRESS_NONE;
host->cpp_where = DCC_CPP_ON_CLIENT;
while (p[0] == ',') {
p++;
if (str_startswith("lzo", p)) {
rs_trace("got LZO option");
host->compr = DCC_COMPRESS_LZO1X;
p += 3;
} else if (str_startswith("down", p)) {
host->is_up = 0;
p += 4;
} else if (str_startswith("cpp", p)) {
rs_trace("got CPP option");
host->cpp_where = DCC_CPP_ON_SERVER;
p += 3;
} else {
rs_log_error("unrecognized option in host specification: %s",
started);
return EXIT_BAD_HOSTSPEC;
}
}
if (dcc_get_protover_from_features(host->compr, host->cpp_where,
&host->protover) == -1) {
rs_log_error("invalid host options: %s", started);
return EXIT_BAD_HOSTSPEC;
}
*psrc = p;
return 0;
}
static int dcc_parse_ssh_host(struct dcc_hostdef *hostdef,
const char *token_start)
{
int ret;
const char *token = token_start;
if ((ret = dcc_dup_part(&token, &hostdef->user, "@")) != 0)
return ret;
if (token[0] != '@') {
rs_log_error("expected '@' to start ssh token");
return EXIT_BAD_HOSTSPEC;
}
token++;
if ((ret = dcc_dup_part(&token, &hostdef->hostname, "/: \t\n\r\f,")) != 0)
return ret;
if (!hostdef->hostname) {
rs_log_error("hostname is required in SSH host specification \"%s\"",
token_start);
return EXIT_BAD_HOSTSPEC;
}
if ((ret = dcc_parse_multiplier(&token, hostdef)) != 0)
return ret;
if (token[0] == ':') {
token++;
if ((ret = dcc_dup_part(&token, &hostdef->ssh_command, " \t\n\r\f,")))
return ret;
}
if ((ret = dcc_parse_options(&token, hostdef)))
return ret;
hostdef->mode = DCC_MODE_SSH;
return 0;
}
static int dcc_parse_tcp_host(struct dcc_hostdef *hostdef,
const char * const token_start)
{
int ret;
const char *token = token_start;
if ((ret = dcc_dup_part(&token, &hostdef->hostname, "/: \t\n\r\f,")))
return ret;
if (!hostdef->hostname) {
rs_log_error("hostname is required in tcp host specification \"%s\"",
token_start);
return EXIT_BAD_HOSTSPEC;
}
if ((ret = dcc_parse_multiplier(&token, hostdef)) != 0)
return ret;
hostdef->port = dcc_default_port;
if (token[0] == ':') {
char *tail;
token++;
hostdef->port = strtol(token, &tail, 10);
if (*tail != '\0' && !isspace(*tail) && *tail != '/' && *tail != ',') {
rs_log_error("invalid tcp port specification in \"%s\"", token);
return EXIT_BAD_HOSTSPEC;
} else {
token = tail;
}
}
if ((ret = dcc_parse_multiplier(&token, hostdef)) != 0)
return ret;
if ((ret = dcc_parse_options(&token, hostdef)))
return ret;
hostdef->mode = DCC_MODE_TCP;
return 0;
}
static int dcc_parse_localhost(struct dcc_hostdef *hostdef,
const char * token_start)
{
int parse_result;
const char *token = token_start + strlen("localhost");
hostdef->mode = DCC_MODE_LOCAL;
hostdef->hostname = strdup("localhost");
hostdef->n_slots = 2;
parse_result = dcc_parse_multiplier(&token, hostdef);
if (hostdef->n_slots > dcc_hostdef_local->n_slots) {
dcc_hostdef_local->n_slots = hostdef->n_slots;
}
return parse_result;
}
int dcc_get_features_from_protover(enum dcc_protover protover,
enum dcc_compress *compr,
enum dcc_cpp_where *cpp_where)
{
if (protover > 1) {
*compr = DCC_COMPRESS_LZO1X;
} else {
*compr = DCC_COMPRESS_NONE;
}
if (protover > 2) {
*cpp_where = DCC_CPP_ON_SERVER;
} else {
*cpp_where = DCC_CPP_ON_CLIENT;
}
if (protover == 0 || protover > 3) {
return 1;
} else {
return 0;
}
}
int dcc_get_protover_from_features(enum dcc_compress compr,
enum dcc_cpp_where cpp_where,
enum dcc_protover *protover)
{
*protover = -1;
if (compr == DCC_COMPRESS_NONE && cpp_where == DCC_CPP_ON_CLIENT) {
*protover = DCC_VER_1;
}
if (compr == DCC_COMPRESS_LZO1X && cpp_where == DCC_CPP_ON_SERVER) {
*protover = DCC_VER_3;
}
if (compr == DCC_COMPRESS_LZO1X && cpp_where == DCC_CPP_ON_CLIENT) {
*protover = DCC_VER_2;
}
if (compr == DCC_COMPRESS_NONE && cpp_where == DCC_CPP_ON_SERVER) {
rs_log_error("pump mode (',cpp') requires compression (',lzo')");
}
return *protover;
}
int dcc_parse_hosts(const char *where, const char *source_name,
struct dcc_hostdef **ret_list,
int *ret_nhosts, struct dcc_hostdef **ret_prev)
{
int ret, flag_randomize = 0;
struct dcc_hostdef *curr, *_prev;
if (!ret_prev) {
ret_prev = &_prev;
_prev = NULL;
}
while (1) {
int token_len;
const char *token_start;
int has_at;
if (where[0] == '\0')
break;
if (where[0] == '#') {
do
where++;
while (where[0] != '\n' && where[0] != '\r' && where[0] != '\0');
continue;
}
if (isspace(where[0])) {
where++;
continue;
}
token_start = where;
token_len = strcspn(where, " #\t\n\f\r");
if (!strncmp(token_start, "--randomize", 11)) {
flag_randomize = 1;
where = token_start + token_len;
continue;
}
if(!strncmp(token_start, "--localslots_cpp", 16)) {
const char *ptr;
ptr = token_start + 16;
if(dcc_parse_multiplier(&ptr, dcc_hostdef_local_cpp) == 0) {
where = token_start + token_len;
continue;
}
}
if(!strncmp(token_start, "--localslots", 12)) {
const char *ptr;
ptr = token_start + 12;
if(dcc_parse_multiplier(&ptr, dcc_hostdef_local) == 0) {
where = token_start + token_len;
continue;
}
}
#ifdef HAVE_AVAHI
if (token_len == sizeof(ZEROCONF_MAGIC)-1 &&
!strncmp(token_start, ZEROCONF_MAGIC, (unsigned) token_len)) {
if ((ret = dcc_zeroconf_add_hosts(ret_list, ret_nhosts, 4, ret_prev) != 0))
return ret;
goto skip;
}
#endif
curr = calloc(1, sizeof(struct dcc_hostdef));
if (!curr) {
rs_log_crit("failed to allocate host definition");
return EXIT_OUT_OF_MEMORY;
}
curr->is_up = 1;
if (!(curr->hostdef_string = my_strndup(token_start, (size_t) token_len))) {
rs_log_crit("failed to allocate hostdef_string");
return EXIT_OUT_OF_MEMORY;
}
if (*ret_prev) {
(*ret_prev)->next = curr;
} else {
*ret_list = curr;
}
curr->n_slots = 4;
curr->protover = DCC_VER_1;
curr->compr = DCC_COMPRESS_NONE;
has_at = (memchr(token_start, '@', (size_t) token_len) != NULL);
if (!strncmp(token_start, "localhost", 9)
&& (token_len == 9 || token_start[9] == '/')) {
rs_trace("found localhost token \"%.*s\"", token_len, token_start);
if ((ret = dcc_parse_localhost(curr, token_start)) != 0)
return ret;
} else if (has_at) {
rs_trace("found ssh token \"%.*s\"", token_len, token_start);
if ((ret = dcc_parse_ssh_host(curr, token_start)) != 0)
return ret;
} else {
rs_trace("found tcp token \"%.*s\"", token_len, token_start);
if ((ret = dcc_parse_tcp_host(curr, token_start)) != 0)
return ret;
}
if (!curr->is_up) {
rs_trace("host %s is down", curr->hostdef_string);
}
(*ret_nhosts)++;
*ret_prev = curr;
#ifdef HAVE_AVAHI
skip:
#endif
where = token_start + token_len;
}
if (*ret_nhosts) {
if (flag_randomize)
if ((ret = dcc_randomize_host_list(ret_list, *ret_nhosts)) != 0)
return ret;
return 0;
} else {
rs_log_warning("%s contained no hosts; can't distribute work", source_name);
return EXIT_BAD_HOSTSPEC;
}
}
int dcc_compare_container(const void *a, const void *b)
{
struct rand_container *i, *j;
i = (struct rand_container *) a;
j = (struct rand_container *) b;
if (i->rand == j->rand)
return 0;
else if (i->rand > j->rand)
return 1;
else
return -1;
}
int dcc_randomize_host_list(struct dcc_hostdef **host_list, int length)
{
int i;
unsigned int rand_val;
struct dcc_hostdef *curr;
struct rand_container *c;
c = malloc(length * sizeof(struct rand_container));
if (!c) {
rs_log_crit("failed to allocate host definition");
return EXIT_OUT_OF_MEMORY;
}
rand_val = (unsigned int) getpid();
srand(rand_val);
curr = *host_list;
for (i = 0; i < length; i++) {
c[i].host = curr;
c[i].rand = rand();
curr = curr->next;
}
qsort(c, length, sizeof(struct rand_container), &dcc_compare_container);
for (i = 0; i < length; i++) {
if (i != length - 1)
c[i].host->next = c[i+1].host;
else
c[i].host->next = NULL;
}
*host_list = c[0].host;
free(c);
return 0;
}
int dcc_free_hostdef(struct dcc_hostdef *host)
{
free(host->user);
free(host->hostname);
free(host->ssh_command);
free(host->hostdef_string);
memset(host, 0xf1, sizeof *host);
free(host);
return 0;
}