/* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*- * * distcc -- A simple distributed compiler system * * Copyright (C) 2002, 2003 by Martin Pool * Copyright 2007 Google Inc. * * 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 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_RESOURCE_H #include #endif #ifdef HAVE_SYS_LOADAVG_H #include #endif #include #include "distcc.h" #include "trace.h" #include "util.h" #include "exitcode.h" #include "snprintf.h" /* I will make a man more precious than fine * gold; even a man than the golden wedge of * Ophir. * -- Isaiah 13:12 */ 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); } /* NB fields must match up for microseconds */ 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)); } /** * Skim through NULL-terminated @p argv, looking for @p s. **/ int argv_contains(char **argv, const char *s) { while (*argv) { if (!strcmp(*argv, s)) return 1; argv++; } return 0; } /** * Redirect a file descriptor into (or out of) a file. * * Used, for example, to catch compiler error messages into a * temporary file. **/ int dcc_redirect_fd(int fd, const char *fname, int mode) { int newfd; /* ignore errors */ 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; } /** * Look up a boolean environment option, which must be either "0" or * "1". The default, if it's not set or is empty, is @p default. **/ 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) == '.')) /* Copy domain part of hostname to static buffer. * If hostname has no domain part, returns -1. * If domain lookup fails, returns -1. * Otherwise places pointer to domain in *domain_name and returns 0. * * This should yield the same result as the linux command * 'dnsdomainname' or 'hostname -d'. **/ int dcc_get_dns_domain(const char **domain_name) { #if 0 /* Too expensive */ 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 /* cheaper */ const char *envh, *envh2; int i; const int MAXDOMAINLEN = 512; /* Kludge for speed: Try to retrieve FQDN from environment. * This can save many milliseconds on a network that's busy and lossy * (glibc retries DNS operations very slowly). */ /* Solaris, BSD tend to put it in HOST. * (Some flavors of Linux put the non-qualified hostname in HOST, * so ignore this if it doesn't have a dot in it.) */ envh = getenv("HOST"); if (envh && !strchr(envh, '.')) envh = NULL; /* Some flavors of Linux put the FQDN in HOSTNAME when * logged in interactively, but not when ssh'd in noninteractively. * Ubuntu's bash puts it in HOSTNAME but doesn't export it! */ envh2 = getenv("HOSTNAME"); if (envh2 && !strchr(envh2, '.')) envh2 = NULL; /* Pick the 'better' of the two. Longer is usually better. */ if (envh2 && (!envh || (strlen(envh) < strlen(envh2)))) envh = envh2; /* If the above didn't work out, fall back to the real way. */ 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 hostname has a dot in it, assume it's the DNS address */ if (!strchr(host_name, '.')) { /* Otherwise ask DNS what our full hostname is */ 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; } /* validate to avoid possible errors from bad chars or huge value */ 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 0 on success, or -1 if the domain name is illegal, e.g. empty */ return ((*domain_name)[0] == '\0') ? -1 : 0; } /** * Set the `FD_CLOEXEC' flag of DESC if VALUE is nonzero, * or clear the flag if VALUE is 0. * * From the GNU C Library examples. * * @returns 0 on success, or -1 on error with `errno' set. **/ int set_cloexec_flag (int desc, int value) { int oldflags = fcntl (desc, F_GETFD, 0); /* If reading the flags failed, return error indication now. */ if (oldflags < 0) return oldflags; /* Set just the flag we want to set. */ if (value != 0) oldflags |= FD_CLOEXEC; else oldflags &= ~FD_CLOEXEC; /* Store modified flag word in the descriptor. */ return fcntl (desc, F_SETFD, oldflags); } /** * Ignore or unignore SIGPIPE. * * The server and child ignore it, because distcc code wants to see * EPIPE errors if something goes wrong. However, for invoked * children it is set back to the default value, because they may not * handle the error properly. **/ 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; } /** * Search through the $PATH looking for a directory containing a file called * @p compiler_name, which is a symbolic link containing the string "distcc". * * Trim the path to just after the *last* such directory. * * If we find a distcc masquerade dir on the PATH, remove all the dirs up * to that point. **/ 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); /* Allocate a buffer that will let us append "/cc" onto any PATH * element, even if there is only one item in the PATH. */ 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; /* ENOENT, EACCESS, etc */ if (!S_ISLNK(sb.st_mode)) break; if ((len = readlink(buf, linkbuf, sizeof linkbuf)) <= 0) continue; linkbuf[len] = '\0'; if (strstr(linkbuf, "distcc")) { /* Set newpath to the part of the PATH past our match. */ newpath = n; } } if (newpath) { int ret = dcc_set_path(newpath); if (ret) return ret; } else rs_trace("not modifying PATH"); free(buf); return 0; } /* Set the PATH environment variable to the indicated value. */ 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; } /* We must leave "buf" allocated. */ return 0; } /* Return the supplied path with the current-working directory prefixed (if * needed) and all "dir/.." references removed. Supply path_len if you want * to use only a substring of the path string, otherwise make it 0. */ char *dcc_abspath(const char *path, int path_len) { static char buf[MAXPATHLEN]; unsigned len; char *p, *slash; if (*path == '/') len = 0; else { char *ret; #ifdef HAVE_GETCWD ret = getcwd(buf, sizeof buf); if (ret == NULL) { rs_log_crit("getcwd failed: %s", strerror(errno)); } #else ret = getwd(buf); if (ret == NULL) { rs_log_crit("getwd failed: %s", strerror(errno)); } #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; } /* Return -1 if a < b, 0 if a == b, and 1 if a > b */ int dcc_timecmp(struct timeval a, struct timeval b) { if (a.tv_sec < b.tv_sec) { return -1; } else if (a.tv_sec > b.tv_sec) { return 1; } else if (a.tv_usec < b.tv_usec) { /* at this point their tv_sec must be the same */ return -1; } else if (a.tv_usec > b.tv_usec) { return 1; } else { /* they must be equal */ return 0; } } /* Return the current number of running processes. */ 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 } /** * Wrapper for getloadavg() that tries to return all 3 samples, and reports * -1 for those samples that are not available. * * Averages are over the last 1, 5, and 15 minutes, respectively. **/ void dcc_getloadavg(double loadavg[3]) { int num; int i; #if defined(HAVE_GETLOADAVG) num = getloadavg(loadavg, 3); #else num = 0; #endif /* If getloadavg() didn't return 3 we want to fill * in the invalid elements with -1 */ if (num < 0) num = 0; for (i=num; i < 3; ++i) loadavg[i] = -1; } /** * Duplicate the part of the string @p psrc up to a character in @p sep * (or end of string), storing the result in @p pdst. @p psrc is updated to * point to the terminator. (If the terminator is not found it will * therefore point to \0. * * If there is no more string, then @p pdst is instead set to NULL, no * memory is allocated, and @p psrc is not advanced. **/ 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; } /* Returns the number of processes in state D, the max non-cc/c++ RSS in kb and * the max RSS program's name */ 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 this doesn't cut it for you, see how CVS does it: * http://savannah.nongnu.org/cgi-bin/viewcvs/cvs/ccvs/lib/getpagesize.h */ 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; /* get rss_size in KB */ if (state == 'D') { (*num_D)++; } l = strlen(RSS_name); c = RSS_name; /* check for .*{++,cc} */ 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 } /* Returns the number of sector read/writes since boot */ 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; /* /proc/diskstats does not exist. probably a 2.4 kernel, so try reading * /proc/partitions */ f = fopen("/proc/partitions", "r"); if (f == NULL) return; kernel26 = 0; } if (!kernel26) /* blast away 2 header lines in /proc/partitions */ { retval = fscanf(f, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s"); if (retval == EOF) { fclose(f); return; } } 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'))) { /* disk stats */ retval = fscanf(f, " %*d %*d %d %*d %*d %*d %d %*d %*d %*d %*d", &reads, &writes); if (retval == EOF || retval != 2) break; /* only add stats for disks, so we don't double count */ *n_reads += reads; *n_writes += writes; } else { #if 0 /* individual parition stats */ retval = fscanf(f, " %*d %d %*d %d", &reads, &writes); if (retval == EOF || retval != 2) break; #endif /* assume the lines aren't longer that 1024 characters */ if (fgets(tmp, 1024, f) == NULL) break; } } fclose(f); #else *n_reads = 0; *n_writes = 0; #endif } #ifndef HAVE_STRLCPY /* like strncpy but does not 0 fill the buffer and always null terminates. bufsize is the size of the destination buffer */ 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 /* Given a string @p input, this function fills a a newly-allocated array of strings with copies of the input's whitespace-separated parts. Returns 0 on success, 1 on error. */ int dcc_tokenize_string(const char *input, char ***argv_ptr) { size_t n_spaces = 0; char *for_count; char **ap; char *input_copy; /* First of all, make a copy of the input string; * this way, we can destroy the copy. */ input_copy = strdup(input); if (input_copy == NULL) return 1; /* Count the spaces in the string. */ for (for_count = input_copy; *for_count; for_count++) if (isspace(*for_count)) n_spaces++; /* The maximum number of space-delimited strings we * can have is n_spaces + 1, and we need to add another 1 for * the null-termination. */ *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 the field is empty, do nothing */ 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; } #ifndef HAVE_GETLINE ssize_t getline(char **lineptr, size_t *n, FILE *stream) { static const int buffer_size_increment = 100; char *buffer; size_t size; size_t bytes_read; int c; char *new_buffer; if (lineptr == NULL || stream == NULL || n == NULL || (*lineptr == NULL && *n != 0)) { /* Invalid parameters. */ return -1; } buffer = *lineptr; size = *n; bytes_read = 0; do { /* Ensure that we have space for next character or '\0'. */ if (bytes_read + 1 > size) { size += buffer_size_increment; new_buffer = realloc(buffer, size); if (new_buffer == NULL) { /* Out of memory. */ *lineptr = buffer; *n = size - buffer_size_increment; return -1; } buffer = new_buffer; } if ((c = fgetc(stream)) == EOF) break; buffer[bytes_read++] = c; } while (c != '\n'); buffer[bytes_read] = '\0'; *lineptr = buffer; *n = size; /* We return -1 on EOF for compatibility with GNU getline(). */ return bytes_read == 0 ? -1 : (ssize_t) bytes_read; } #endif