#include <config.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/time.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#ifdef HAVE_SYS_LOADAVG_H
#include <sys/loadavg.h>
#endif
#include <sys/param.h>
#include "distcc.h"
#include "trace.h"
#include "util.h"
#include "exitcode.h"
#include "snprintf.h"
void dcc_exit(int exitcode)
{
struct rusage self_ru, children_ru;
if (getrusage(RUSAGE_SELF, &self_ru)) {
rs_log_warning("getrusage(RUSAGE_SELF) failed: %s", strerror(errno));
memset(&self_ru, 0, sizeof self_ru);
}
if (getrusage(RUSAGE_CHILDREN, &children_ru)) {
rs_log_warning("getrusage(RUSAGE_CHILDREN) failed: %s", strerror(errno));
memset(&children_ru, 0, sizeof children_ru);
}
rs_log(RS_LOG_INFO,
"exit: code %d; self: %d.%06d user %d.%06d sys; children: %d.%06d user %d.%06d sys",
exitcode,
(int) self_ru.ru_utime.tv_sec, (int) self_ru.ru_utime.tv_usec,
(int) self_ru.ru_stime.tv_sec, (int) self_ru.ru_stime.tv_usec,
(int) children_ru.ru_utime.tv_sec, (int) children_ru.ru_utime.tv_usec,
(int) children_ru.ru_stime.tv_sec, (int) children_ru.ru_stime.tv_usec);
exit(exitcode);
}
int str_endswith(const char *tail, const char *tiger)
{
size_t len_tail = strlen(tail);
size_t len_tiger = strlen(tiger);
if (len_tail > len_tiger)
return 0;
return !strcmp(tiger + len_tiger - len_tail, tail);
}
int str_startswith(const char *head, const char *worm)
{
return !strncmp(head, worm, strlen(head));
}
int argv_contains(char **argv, const char *s)
{
while (*argv) {
if (!strcmp(*argv, s))
return 1;
argv++;
}
return 0;
}
int dcc_redirect_fd(int fd, const char *fname, int mode)
{
int newfd;
close(fd);
newfd = open(fname, mode, 0666);
if (newfd == -1) {
rs_log_crit("failed to reopen fd%d onto %s: %s",
fd, fname, strerror(errno));
return EXIT_IO_ERROR;
} else if (newfd != fd) {
rs_log_crit("oops, reopened fd%d onto fd%d?", fd, newfd);
return EXIT_IO_ERROR;
}
return 0;
}
char *dcc_gethostname(void)
{
static char myname[100] = "\0";
if (!myname[0]) {
if (gethostname(myname, sizeof myname - 1) == -1)
strcpy(myname, "UNKNOWN");
}
return myname;
}
int dcc_getenv_bool(const char *name, int default_value)
{
const char *e;
e = getenv(name);
if (!e || !*e)
return default_value;
if (!strcmp(e, "1"))
return 1;
else if (!strcmp(e, "0"))
return 0;
else
return default_value;
}
#define IS_LEGAL_DOMAIN_CHAR(c) (isalnum(c) || ((c) == '-') || ((c) == '.'))
int dcc_get_dns_domain(const char **domain_name)
{
#if 0
static char host_name[1024];
struct hostent *h;
int ret;
ret = gethostname(host_name, sizeof(host_name));
if (ret != 0)
return -1;
h = gethostbyname(host_name);
if (h == NULL) {
rs_log_error("failed to look up self \"%s\": %s", host_name,
hstrerror(h_errno));
return -1;
}
strncpy(host_name, h->h_name, sizeof(host_name));
*domain_name = strchr(h->h_name, '.');
#else
const char *envh, *envh2;
int i;
const int MAXDOMAINLEN = 512;
envh = getenv("HOST");
if (envh && !strchr(envh, '.'))
envh = NULL;
envh2 = getenv("HOSTNAME");
if (envh2 && !strchr(envh2, '.'))
envh2 = NULL;
if (envh2 && (!envh || (strlen(envh) < strlen(envh2))))
envh = envh2;
if (!envh || !strchr(envh, '.')) {
static char host_name[1024];
struct hostent *h;
int ret;
ret = gethostname(host_name, sizeof(host_name));
if (ret != 0)
return -1;
if (!strchr(host_name, '.')) {
h = gethostbyname(host_name);
if (h == NULL) {
rs_log_error("failed to look up self \"%s\": %s", host_name,
hstrerror(h_errno));
return -1;
}
strncpy(host_name, h->h_name, sizeof(host_name));
}
envh = host_name;
}
for (i=0; envh[i] != '\0'; i++) {
if (i > MAXDOMAINLEN || !IS_LEGAL_DOMAIN_CHAR(envh[i])) {
rs_log_error("HOST/HOSTNAME present in environment but illegal: '%s'", envh);
return -1;
}
}
*domain_name = strchr(envh, '.');
#endif
if (*domain_name == NULL)
return -1;
(*domain_name)++;
return ((*domain_name)[0] == '\0') ? -1 : 0;
}
int set_cloexec_flag (int desc, int value)
{
int oldflags = fcntl (desc, F_GETFD, 0);
if (oldflags < 0)
return oldflags;
if (value != 0)
oldflags |= FD_CLOEXEC;
else
oldflags &= ~FD_CLOEXEC;
return fcntl (desc, F_SETFD, oldflags);
}
int dcc_ignore_sigpipe(int val)
{
if (signal(SIGPIPE, val ? SIG_IGN : SIG_DFL) == SIG_ERR) {
rs_log_warning("signal(SIGPIPE, %s) failed: %s",
val ? "ignore" : "default",
strerror(errno));
return EXIT_DISTCC_FAILED;
}
return 0;
}
int dcc_trim_path(const char *compiler_name)
{
const char *envpath, *newpath, *p, *n;
char linkbuf[MAXPATHLEN], *buf;
struct stat sb;
size_t len;
if (!(envpath = getenv("PATH"))) {
rs_trace("PATH seems not to be defined");
return 0;
}
rs_trace("original PATH %s", envpath);
rs_trace("looking for \"%s\"", compiler_name);
if (!(buf = malloc(strlen(envpath)+1+strlen(compiler_name)+1))) {
rs_log_error("failed to allocate buffer for PATH munging");
return EXIT_OUT_OF_MEMORY;
}
for (n = p = envpath, newpath = NULL; *n; p = n) {
n = strchr(p, ':');
if (n)
len = n++ - p;
else {
len = strlen(p);
n = p + len;
}
strncpy(buf, p, len);
sprintf(buf + len, "/%s", compiler_name);
if (lstat(buf, &sb) == -1)
continue;
if (!S_ISLNK(sb.st_mode))
break;
if ((len = readlink(buf, linkbuf, sizeof linkbuf)) <= 0)
continue;
linkbuf[len] = '\0';
if (strstr(linkbuf, "distcc")) {
newpath = n;
}
}
if (newpath) {
int ret = dcc_set_path(newpath);
if (ret)
return ret;
} else
rs_trace("not modifying PATH");
free(buf);
return 0;
}
int dcc_set_path(const char *newpath)
{
char *buf;
if (asprintf(&buf, "PATH=%s", newpath) <= 0 || !buf) {
rs_log_error("failed to allocate buffer for new PATH");
return EXIT_OUT_OF_MEMORY;
}
rs_trace("setting %s", buf);
if (putenv(buf) < 0) {
rs_log_error("putenv PATH failed");
return EXIT_FAILURE;
}
return 0;
}
char *dcc_abspath(const char *path, int path_len)
{
static char buf[MAXPATHLEN];
unsigned len;
char *p, *slash;
if (*path == '/')
len = 0;
else {
#ifdef HAVE_GETCWD
getcwd(buf, sizeof buf);
#else
getwd(buf);
#endif
len = strlen(buf);
if (len >= sizeof buf) {
rs_log_crit("getwd overflowed in dcc_abspath()");
}
buf[len++] = '/';
}
if (path_len <= 0)
path_len = strlen(path);
if (path_len >= 2 && *path == '.' && path[1] == '/') {
path += 2;
path_len -= 2;
}
if (len + (unsigned)path_len >= sizeof buf) {
rs_log_error("path overflowed in dcc_abspath()");
exit(EXIT_OUT_OF_MEMORY);
}
strncpy(buf + len, path, path_len);
buf[len + path_len] = '\0';
for (p = buf+len-(len > 0); (p = strstr(p, "/../")) != NULL; p = slash) {
*p = '\0';
if (!(slash = strrchr(buf, '/')))
slash = p;
strcpy(slash, p+3);
}
return buf;
}
int dcc_getcurrentload(void) {
#if defined(linux)
double stats[3];
int running;
int total;
int last_pid;
int retval;
FILE *f = fopen("/proc/loadavg", "r");
if (NULL == f)
return -1;
retval = fscanf(f, "%lf %lf %lf %d/%d %d", &stats[0], &stats[1], &stats[2],
&running, &total, &last_pid);
fclose(f);
if (6 != retval)
return -1;
return running;
#else
return -1;
#endif
}
void dcc_getloadavg(double loadavg[3]) {
int num;
int i;
#if defined(HAVE_GETLOADAVG)
num = getloadavg(loadavg, 3);
#else
num = 0;
#endif
if (num < 0)
num = 0;
for (i=num; i < 3; ++i)
loadavg[i] = -1;
}
int dcc_dup_part(const char **psrc, char **pdst, const char *sep)
{
size_t len;
len = strcspn(*psrc, sep);
if (len == 0) {
*pdst = NULL;
} else {
if (!(*pdst = malloc(len + 1))) {
rs_log_error("failed to allocate string duplicate: %d", (int) len);
return EXIT_OUT_OF_MEMORY;
}
strncpy(*pdst, *psrc, len);
(*pdst)[len] = '\0';
(*psrc) += len;
}
return 0;
}
int dcc_remove_if_exists(const char *fname)
{
if (unlink(fname) && errno != ENOENT) {
rs_log_warning("failed to unlink %s: %s", fname,
strerror(errno));
return EXIT_IO_ERROR;
}
return 0;
}
void dcc_get_proc_stats(int *num_D, int *max_RSS, char **max_RSS_name) {
#if defined(linux)
DIR *proc = opendir("/proc");
struct dirent *procsubdir;
static int pagesize = -1;
static char RSS_name[1024];
char statfile[1024];
FILE *f;
char name[1024];
char state;
int pid;
int rss_size;
int l;
char *c;
int isCC;
if (pagesize == -1) {
#if HAVE_GETPAGESIZE
pagesize = getpagesize();
#else
pagesize = 8192;
#endif
}
*num_D = 0;
*max_RSS = 0;
*max_RSS_name = RSS_name;
RSS_name[0] = 0;
while ((procsubdir = readdir(proc)) != NULL) {
if (sscanf(procsubdir->d_name, "%d", &pid) != 1)
continue;
strcpy(statfile, "/proc/");
strcat(statfile, procsubdir->d_name);
strcat(statfile, "/stat");
f = fopen(statfile, "r");
if (f == NULL)
continue;
if (fscanf(f, "%*d %s %c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d",
name, &state, &rss_size) != 3) {
fclose(f);
continue;
}
rss_size = (rss_size * pagesize) / 1024;
if (state == 'D') {
(*num_D)++;
}
l = strlen(RSS_name);
c = RSS_name;
isCC = (l >= 2) && ((c[l-1] == 'c' && c[l-2] == 'c')
|| (c[l-1] == '+' && c[l-2] == '+'));
if ((rss_size > *max_RSS) && !isCC) {
*max_RSS = rss_size;
strncpy(RSS_name, name, 1024);
}
fclose(f);
}
closedir(proc);
#else
static char RSS_name[] = "none";
*num_D = -1;
*max_RSS = -1;
*max_RSS_name = RSS_name;
#endif
}
void dcc_get_disk_io_stats(int *n_reads, int *n_writes) {
#if defined(linux)
int retval;
int kernel26 = 1;
FILE *f;
int reads, writes, minor;
char dev[100];
char tmp[1024];
*n_reads = 0;
*n_writes = 0;
f = fopen("/proc/diskstats", "r");
if (f == NULL) {
if (errno != ENOENT)
return;
f = fopen("/proc/partitions", "r");
if (f == NULL)
return;
kernel26 = 0;
}
if (!kernel26)
fscanf(f, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s");
while (1) {
if (kernel26)
retval = fscanf(f, " %*d %d %s", &minor, dev);
else
retval = fscanf(f, " %*d %d %*d %s", &minor, dev);
if (retval == EOF || retval != 2)
break;
if (minor % 64 == 0
&& ((dev[0] == 'h' && dev[1] == 'd' && dev[2] == 'a')
|| (dev[0] == 's' && dev[1] == 'd' && dev[2] == 'a'))) {
retval = fscanf(f, " %*d %*d %d %*d %*d %*d %d %*d %*d %*d %*d",
&reads, &writes);
if (retval == EOF || retval != 2)
break;
*n_reads += reads;
*n_writes += writes;
} else {
#if 0
retval = fscanf(f, " %*d %d %*d %d", &reads, &writes);
if (retval == EOF || retval != 2)
break;
#endif
fgets(tmp, 1024, f);
}
}
fclose(f);
#else
*n_reads = 0;
*n_writes = 0;
#endif
}
#ifndef HAVE_STRLCPY
size_t strlcpy(char *d, const char *s, size_t bufsize)
{
size_t len = strlen(s);
size_t ret = len;
if (bufsize <= 0) return 0;
if (len >= bufsize) len = bufsize-1;
memcpy(d, s, len);
d[len] = 0;
return ret;
}
#endif
#ifndef HAVE_STRSEP
static char* strsep(char** str, const char* delims)
{
char* token;
if (*str == NULL) {
return NULL;
}
token = *str;
while (**str != '\0') {
if (strchr(delims, **str) != NULL) {
**str = '\0';
(*str)++;
return token;
}
(*str)++;
}
*str = NULL;
return token;
}
#endif
int dcc_tokenize_string(const char *input, char ***argv_ptr)
{
size_t n_spaces = 0;
char *for_count;
char **ap;
char *input_copy;
input_copy = strdup(input);
if (input_copy == NULL)
return 1;
for (for_count = input_copy; *for_count; for_count++)
if (isspace(*for_count))
n_spaces++;
*argv_ptr = malloc(sizeof(char*) * (n_spaces + 1 + 1));
if (*argv_ptr == NULL) {
free(input_copy);
return 1;
}
ap = *argv_ptr;
while((*ap = strsep(&input_copy, " \t\n")) != NULL) {
if (**ap == '\0')
continue;
*ap = strdup(*ap);
if (*ap == NULL) {
char **p;
for (p = *argv_ptr; *p; p++) {
free(*p);
}
free(*argv_ptr);
free(input_copy);
return 1;
}
ap++;
}
free(input_copy);
return 0;
}
char *dcc_replace_substring(const char *s,
const char *find, const char *replace) {
int s_left;
int buf_pos = 0, buf_size = 0;
char *buf = NULL, *new_buf;
const char *next;
int replace_len, find_len;
int len_change;
if (!s || !find || !replace) {
rs_log_error("got NULL arguments");
goto out_error;
}
find_len = strlen(find);
replace_len = strlen(replace);
if (!find_len) {
rs_log_error("Asked to replace an empty string");
goto out_error;
}
len_change = replace_len - find_len;
if (len_change < 0)
len_change = 0;
s_left = strlen(s);
buf_size = s_left + 1;
buf = malloc(buf_size * sizeof(char));
if (!buf) {
rs_log_error("malloc(%ld) failed: %s",
(long)buf_size * sizeof(char), strerror(errno));
goto out_error;
}
while ((next = strstr(s, find))) {
if (len_change) {
buf_size += len_change;
new_buf = realloc(buf, buf_size * sizeof(char));
if (!new_buf) {
rs_log_error("realloc(%ld) failed: %s",
(long)buf_size * sizeof(char), strerror(errno));
goto out_error;
}
buf = new_buf;
}
strncpy(buf + buf_pos, s, next - s);
buf_pos += (next - s);
s_left -= (next - s);
strcpy(buf + buf_pos, replace);
buf_pos += replace_len;
s_left -= find_len;
s = next + find_len;
}
if (s_left) {
strcpy(buf + buf_pos, s);
buf_pos += s_left;
}
buf[buf_pos++] = '\0';
if (buf_pos < buf_size) {
buf_size = buf_pos;
new_buf = realloc(buf, buf_size * sizeof(char));
if (new_buf) {
buf = new_buf;
} else {
rs_log_info("realloc(%ld) failed: %s",
(long)buf_size * sizeof(char), strerror(errno));
}
}
return buf;
out_error:
if (buf)
free(buf);
return NULL;
}