#include <config.h>
#ifdef XCODE_INTEGRATION
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fnmatch.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/utsname.h>
#include "daemon.h"
#include "distcc.h"
#include "dopt.h"
#include "trace.h"
#include "util.h"
#include "xci.h"
typedef struct _dcc_xci_compiler_info {
char *raw_path;
char *absolute_path;
char *version;
struct _dcc_xci_compiler_info *next;
} dcc_xci_compiler_info;
static const char *dcc_xci_get_system_version(void) {
static const char product_line_match[] = "ProductVersion:";
static const char build_line_match[] = "BuildVersion:";
struct utsname info;
struct stat statbuf;
char *sw_vers = NULL;
char *product_line, *build_line, *product, *build, *end;
int len;
static int has_system_version = 0;
static char *system_version = NULL;
if (arg_system_version)
return arg_system_version;
if (!has_system_version) {
has_system_version = 1;
if (uname(&info)) {
rs_log_error("uname() failed: %s", strerror(errno));
goto out_error;
}
#ifdef __APPLE__
if (!strncmp(info.machine, "Power Macintosh", sizeof(info.machine))) {
strncpy(info.machine, "ppc", sizeof(info.machine));
info.machine[sizeof(info.machine) - 1] = '\0';
}
#endif
if (stat("/usr/bin/sw_vers", &statbuf) == 0) {
if (!(sw_vers = dcc_xci_run_command("/usr/bin/sw_vers")))
goto out_error;
product_line = strstr(sw_vers, product_line_match);
build_line = strstr(sw_vers, build_line_match);
if (!product_line || !build_line ||
!(product_line == sw_vers || *(product_line - 1) == '\n') ||
!(build_line == sw_vers || *(build_line - 1) == '\n')) {
rs_log_error("malformed output from sw_vers");
goto out_error;
}
product = product_line + sizeof(product_line_match);
while (*product && *product != '\n' && isspace(*product))
++product;
build = build_line + sizeof(build_line_match);
while (*build && *build != '\n' && isspace(*build))
++build;
if (!*product || *product == '\n' || !*build || *build == '\n') {
rs_log_error("malformed output from sw_vers");
goto out_error;
}
end = strchr(product, '\n');
if (!end) {
rs_log_error("malformed output from sw_vers");
goto out_error;
}
*end = '\0';
end = strchr(build, '\n');
if (!end) {
rs_log_error("malformed output from sw_vers");
goto out_error;
}
*end = '\0';
len = strlen(product) + strlen(build) + strlen(info.machine) + 6;
if (!(system_version = malloc(len))) {
rs_log_error("malloc() failed: %s", strerror(errno));
goto out_error;
}
snprintf(system_version, len, "%s (%s, %s)",
product, build, info.machine);
free(sw_vers);
sw_vers = NULL;
} else {
len = strlen(info.sysname) + strlen(info.release) +
strlen(info.machine) + 5;
if (!(system_version = malloc(len))) {
rs_log_error("malloc() failed: %s", strerror(errno));
goto out_error;
}
snprintf(system_version, len, "%s %s (%s)",
info.sysname, info.release, info.machine);
}
}
return system_version;
out_error:
if (sw_vers)
free(sw_vers);
return NULL;
}
static char *dcc_xci_selected_prefix(void) {
const char *xcodeselect_path;
char *selected_prefix = NULL;
int size;
xcodeselect_path = dcc_xci_xcodeselect_path();
if (!xcodeselect_path) {
rs_log_error("failed to get xcode-select path");
return NULL;
}
size = strlen(xcodeselect_path) + sizeof(PREFIXDIR);
selected_prefix = malloc(size);
if (!selected_prefix) {
rs_log_error("malloc() failed: %s", strerror(errno));
return NULL;
}
snprintf(selected_prefix, size, "%s%s", xcodeselect_path, PREFIXDIR);
return selected_prefix;
}
static char *dcc_xci_path_in_prefix(const char *path) {
char *prefix = NULL, *prefix_path = NULL;
size_t prefix_len, path_len;
if (path[0] == '/') {
char *rv = strdup(path);
if (!rv)
rs_log_error("strdup(\"%s\") failed: %s", path, strerror(errno));
return rv;
}
prefix = dcc_xci_selected_prefix();
if (!prefix)
goto out_error;
prefix_len = strlen(prefix);
path_len = strlen(path);
prefix_path = realloc(prefix, prefix_len + path_len + 2);
if (!prefix_path)
goto out_error;
prefix = NULL;
prefix_path[prefix_len] = '/';
strncpy(prefix_path + prefix_len + 1, path, path_len + 1);
return prefix_path;
out_error:
if (prefix)
free(prefix);
return NULL;
}
static const dcc_xci_compiler_info *dcc_xci_parse_distcc_compilers(void) {
static int parsed_compilers = 0;
static dcc_xci_compiler_info *compilers = NULL;
char *compilers_path = NULL;
FILE *compilers_file = NULL;
char *compilers_data = NULL;
char *line, *newline;
size_t len, pos, line_len;
dcc_xci_compiler_info *ci = NULL, *last_ci = NULL;
struct stat statbuf;
char *cmd = NULL, *version_output = NULL;
if (parsed_compilers)
return compilers;
parsed_compilers = 1;
compilers_path = dcc_xci_path_in_prefix("share/distcc_compilers");
if (!compilers_path)
goto out_error;
if (!(compilers_file = fopen(compilers_path, "r"))) {
rs_log_error("fopen(\"%s\", \"r\") failed: %s",
compilers_path, strerror(errno));
goto out_error;
}
if (!(compilers_data = dcc_xci_read_whole_file(compilers_file, &len))) {
rs_log_error("dcc_xci_read_whole_file failed for \"%s\"",
compilers_path);
goto out_error;
}
for (pos = 0; pos < len; pos += line_len + 1) {
line = compilers_data + pos;
newline = strchr(line, '\n');
if (newline) {
*newline = '\0';
line_len = newline - line;
} else {
line_len = strlen(line);
}
if (!line_len || line[0] == '#')
continue;
ci = calloc(1, sizeof(dcc_xci_compiler_info));
if (!ci) {
rs_log_error("calloc() failed: %s", strerror(errno));
goto out_error;
}
ci->raw_path = strdup(line);
if (!ci->raw_path) {
rs_log_error("strdup(\"%s\") failed: %s", line, strerror(errno));
goto out_error;
}
if (!(ci->absolute_path = dcc_xci_path_in_prefix(ci->raw_path)))
goto out_error;
if (stat(ci->absolute_path, &statbuf) == 0) {
static const char version_args[] = " -v 2>&1";
size_t cmd_len = strlen(ci->absolute_path) + sizeof(version_args);
if (!(cmd = malloc(cmd_len))) {
rs_log_error("malloc() failed: %s\n", strerror(errno));
goto out_error;
}
snprintf(cmd, cmd_len, "%s%s", ci->absolute_path, version_args);
if (!(version_output = dcc_xci_run_command(cmd)))
goto next_compiler;
char *v_line_start = version_output;
while (v_line_start && v_line_start[0] != '\0') {
char *v_line_end = strchr(v_line_start, '\n');
char *v_line;
if (!v_line_end) {
if (!(v_line = strdup(v_line_start))) {
rs_log_error("strdup() failed: %s\n", strerror(errno));
goto out_error;
}
} else {
if (!(v_line = malloc(v_line_end - v_line_start + 1))) {
rs_log_error("malloc() failed: %s\n", strerror(errno));
goto out_error;
}
strncpy(v_line, v_line_start, v_line_end - v_line_start);
v_line[v_line_end - v_line_start] = '\0';
}
static const char version_pattern[] = " version ";
char *version = strstr(v_line, version_pattern);
if (version) {
ci->version = v_line;
break;
} else {
free(v_line);
}
v_line_start = v_line_end;
if (v_line_start && v_line_start[0] != '\0')
++v_line_start;
}
if (!ci->version) {
rs_log_warning("could not determine end of version for \"%s\"",
ci->absolute_path);
goto next_compiler;
}
if (!compilers)
compilers = ci;
else
last_ci->next = ci;
last_ci = ci;
ci = NULL;
free(cmd);
cmd = NULL;
free(version_output);
version_output = NULL;
} else {
next_compiler:
free(ci->absolute_path);
free(ci->raw_path);
free(ci);
ci = NULL;
}
}
free(compilers_data);
compilers_data = NULL;
if (fclose(compilers_file)) {
rs_log_error("fclose(\"%s\") failed: %s",
compilers_path, strerror(errno));
}
compilers_file = NULL;
free(compilers_path);
compilers_path = NULL;
return compilers;
out_error:
if (version_output)
free(version_output);
if (cmd)
free(cmd);
if (ci) {
if (ci->version)
free (ci->version);
if (ci->absolute_path)
free (ci->absolute_path);
if (ci->raw_path)
free (ci->raw_path);
free(ci);
}
ci = compilers;
while (ci) {
if (ci->version)
free (ci->version);
if (ci->absolute_path)
free (ci->absolute_path);
if (ci->raw_path)
free (ci->raw_path);
last_ci = ci;
ci = ci->next;
free(last_ci);
}
compilers = NULL;
if (compilers_data)
free(compilers_data);
if (compilers_file)
fclose(compilers_file);
if (compilers_path)
free(compilers_path);
return NULL;
}
static char **dcc_xci_get_all_compiler_versions(void) {
const dcc_xci_compiler_info *compilers, *ci;
int count = 0, i, j;
char **result = NULL;
compilers = dcc_xci_parse_distcc_compilers();
if (!compilers)
return NULL;
for (ci = compilers; ci; ci = ci->next)
++count;
result = malloc((count + 1) * sizeof(char *));
if (!result) {
rs_log_error("malloc() failed: %s\n", strerror(errno));
return NULL;
}
for (i = 0, ci = compilers; ci; ci = ci->next) {
for (j = 0; j < i; j++) {
if (!strcmp(result[j], ci->version))
break;
}
if (j == i)
result[i++] = ci->version;
}
result[i++] = NULL;
return result;
}
static char **dcc_xci_directory_list(const char *dir_path, const char *pattern,
int match_type) {
DIR *dir = NULL;
struct dirent *entry;
struct dirent entry_storage;
char **result = NULL;
char **new_result;
int count = 0;
int dir_len, name_size;
int err = 0;
if (!dir_path) goto out_error;
dir_len = strlen(dir_path);
if (!(dir = opendir(dir_path))) {
rs_log_error("opendir(\"%s\") failed: %s", dir_path, strerror(errno));
goto out_error;
}
while (err == 0) {
if ((err = readdir_r(dir, &entry_storage, &entry))) {
rs_log_error("readdir_r() failed: %d: %s", err, strerror(err));
goto out_error;
}
if (!entry)
break;
if (match_type && (match_type != entry->d_type))
continue;
if (pattern && fnmatch(pattern, entry->d_name, 0))
continue;
new_result = realloc(result, (count + 2) * sizeof(char*));
if (!new_result) {
rs_log_error("realloc() failed: %s", strerror(errno));
goto out_error;
}
result = new_result;
name_size = dir_len + 2 + strlen(entry->d_name);
result[count] = malloc(name_size);
if (!result[count]) {
rs_log_error("malloc() failed: %s", strerror(errno));
goto out_error;
}
snprintf(result[count++], name_size, "%s/%s", dir_path, entry->d_name);
}
if (count)
result[count] = NULL;
if (closedir(dir)) {
rs_log_error("closedir(\"%s\") failed: %s", dir_path, strerror(errno));
}
return result;
out_error:
if (dir)
closedir(dir);
if (result) {
while (count--) {
if (result[count])
free(result[count]);
}
free(result);
}
return NULL;
}
static char *dcc_xci_plist_string_value(const char *plist, const char *key) {
const char *s, *value_start;
const char key_begin[] = "<key>";
const char key_end[] = "</key>";
const char marker_begin[] = "<string>";
const char marker_end[] = "</string>";
char *find, *value = NULL;
size_t find_size;
int value_len;
find_size = sizeof(key_begin) + strlen(key) + sizeof(key_end) - 1;
if (!(find = malloc(find_size * sizeof(char)))) {
rs_log_error("malloc() failed: %s", strerror(errno));
goto bail_out;
}
snprintf(find, find_size, "%s%s%s", key_begin, key, key_end);
if (!(s = strstr(plist, find))) {
rs_log_error("key \"%s\" wasn't found", key);
goto bail_out;
}
s += strlen(find);
while (*s && isspace(*s)) ++s;
if (strncmp(s, marker_begin, sizeof(marker_begin) - 1)) {
rs_log_error("Failed to find expected \"%s\"", marker_begin);
goto bail_out;
}
s += sizeof(marker_begin) - 1;
value_start = s;
if (!(s = strstr(s, marker_end))) {
rs_log_error("Failed to find expected \"%s\"", marker_end);
goto bail_out;
}
value_len = s - value_start;
value = malloc((value_len + 1) * sizeof(char));
if (!value) {
rs_log_error("malloc() failed: %s", strerror(errno));
goto bail_out;
}
strncpy(value, value_start, value_len);
value[value_len] = '\0';
bail_out:
if (find)
free(find);
return value;
}
static char *dcc_xci_create_sdk_info(const char *path) {
const char *sdk_path;
char *name = NULL, *version = NULL, *build_version = NULL, *info = NULL;
const char *xcode_dev_dir;
char buf[PATH_MAX + 1];
FILE *plist_file = NULL;
char *plist = NULL;
int info_size, xcode_dev_dir_len;
xcode_dev_dir = dcc_xci_xcodeselect_path();
xcode_dev_dir_len = strlen(xcode_dev_dir);
if ((strncmp(path, xcode_dev_dir, xcode_dev_dir_len) != 0)
|| (path[xcode_dev_dir_len] != '/')) {
rs_log_error("\"%s\" is not in the Xcode developer dir (%s)",
path, xcode_dev_dir);
goto bail_out;
}
sdk_path = path + xcode_dev_dir_len + 1;
snprintf(buf, sizeof(buf), "%s/SDKSettings.plist", path);
if (!(plist_file = fopen(buf, "r"))) {
rs_log_error("fopen(%s) failed: %s", buf, strerror(errno));
goto bail_out;
}
if (!(plist = dcc_xci_read_whole_file(plist_file, NULL))) {
rs_log_error("Failed to read all of \"%s\"", buf);
goto bail_out;
}
fclose(plist_file);
plist_file = NULL;
name = dcc_xci_plist_string_value(plist, "CanonicalName");
if (!name) {
rs_log_error("%s didn't have CanonicalName", buf);
goto bail_out;
}
free(plist);
plist = NULL;
snprintf(buf, sizeof(buf),
"%s/System/Library/CoreServices/SystemVersion.plist", path);
if (!(plist_file = fopen(buf, "r"))) {
rs_log_error("fopen(%s) failed: %s", buf, strerror(errno));
goto bail_out;
}
if (!(plist = dcc_xci_read_whole_file(plist_file, NULL))) {
rs_log_error("Failed to read all of \"%s\"", buf);
goto bail_out;
}
fclose(plist_file);
plist_file = NULL;
version = dcc_xci_plist_string_value(plist, "ProductVersion");
if (!version) {
rs_log_error("%s didn't have ProductVersion", buf);
goto bail_out;
}
build_version = dcc_xci_plist_string_value(plist, "ProductBuildVersion");
if (!build_version) {
rs_log_error("%s didn't have ProductBuildVersion", buf);
goto bail_out;
}
info_size = strlen(name) + 1 + strlen(version) + 1 +
strlen(build_version) + 1 + strlen(sdk_path) + 1;
info = malloc(info_size * sizeof(char));
if (!info) {
rs_log_error("malloc(%d) failed: %s", info_size, strerror(errno));
goto bail_out;
}
snprintf(info, info_size, "%s %s %s %s",
name, version, build_version, sdk_path);
bail_out:
if (name)
free(name);
if (version)
free(version);
if (build_version)
free(build_version);
if (plist)
free(plist);
if (plist_file)
fclose(plist_file);
return info;
}
static char **dcc_xci_scan_developer_sdks(void) {
char **sdks = NULL, **new_sdks;
int sdk_count = 0;
const char *dev_dir;
char buf[PATH_MAX + 1];
char **sdk_paths = NULL;
char **platforms = NULL;
char *info = NULL;
int i, j;
struct stat statbuf;
dev_dir = dcc_xci_xcodeselect_path();
if (!dev_dir) goto bail_out;
snprintf(buf, sizeof(buf), "%s/SDKs", dev_dir);
if (stat(buf, &statbuf) == 0) {
sdk_paths = dcc_xci_directory_list(buf, "*.sdk", DT_DIR);
if (sdk_paths) {
for (i = 0 ; sdk_paths[i] ; ++i) {
info = dcc_xci_create_sdk_info(sdk_paths[i]);
if (!info) {
rs_log_error("failed to read sdk info, path: %s",
sdk_paths[i]);
continue;
}
new_sdks = realloc(sdks, (sdk_count + 2) * sizeof(char*));
if (!new_sdks) {
rs_log_error("realloc() failed: %s", strerror(errno));
dcc_free_argv(sdks);
sdks = NULL;
goto bail_out;
}
sdks = new_sdks;
sdks[sdk_count++] = info;
info = NULL;
sdks[sdk_count] = NULL;
}
dcc_free_argv(sdk_paths);
sdk_paths = NULL;
}
}
snprintf(buf, sizeof(buf), "%s/Platforms", dev_dir);
if (stat(buf, &statbuf) == 0) {
platforms = dcc_xci_directory_list(buf, "*.platform", DT_DIR);
if (platforms) {
for (i = 0 ; platforms[i] ; ++i) {
snprintf(buf, sizeof(buf), "%s/Developer/SDKs", platforms[i]);
if (stat(buf, &statbuf) != 0)
continue;
sdk_paths = dcc_xci_directory_list(buf, "*.sdk", DT_DIR);
if (!sdk_paths)
continue;
for (j = 0 ; sdk_paths[j] ; ++j) {
info = dcc_xci_create_sdk_info(sdk_paths[j]);
if (!info) {
rs_log_error("failed to read sdk info, path: %s",
sdk_paths[j]);
continue;
}
new_sdks = realloc(sdks, (sdk_count + 2) * sizeof(char*));
if (!new_sdks) {
rs_log_error("realloc() failed: %s", strerror(errno));
dcc_free_argv(sdks);
sdks = NULL;
goto bail_out;
}
sdks = new_sdks;
sdks[sdk_count++] = info;
info = NULL;
sdks[sdk_count] = NULL;
}
dcc_free_argv(sdk_paths);
sdk_paths = NULL;
}
dcc_free_argv(platforms);
platforms = NULL;
}
}
bail_out:
if (info)
free(info);
if (sdk_paths)
dcc_free_argv(sdk_paths);
if (platforms)
dcc_free_argv(platforms);
return sdks;
}
const char *dcc_xci_host_info_string() {
static const char sys_key[] = "SYSTEM=";
static const char distcc_key_and_value[] = "DISTCC=" PACKAGE_VERSION;
static const char compiler_key[] = "COMPILER=";
static const char cpus_key[] = "CPUS=";
static const char cpuspeed_key[] = "CPUSPEED=";
static const char jobs_key[] = "JOBS=";
static const char priority_key[] = "PRIORITY=";
static const char sdk_key[] = "SDK=";
static int has_host_info = 0;
static char *host_info = NULL;
int len = 0, pos = 0, ncpus;
unsigned long long cpuspeed;
char *info = NULL;
const char *sys;
char **compilers = NULL, **compiler;
char **sdks, **sdk = NULL;
if (has_host_info)
return host_info;
has_host_info = 1;
static const int int_decimal_len = 20;
sys = dcc_xci_get_system_version();
if (sys)
len += sizeof(sys_key) + strlen(sys);
len += sizeof(distcc_key_and_value);
compilers = dcc_xci_get_all_compiler_versions();
if (compilers) {
for (compiler = compilers; *compiler; ++compiler)
len += sizeof(compiler_key) + strlen(*compiler);
}
len += sizeof(cpus_key) + int_decimal_len;
len += sizeof(cpuspeed_key) + int_decimal_len;
if (dcc_max_kids)
len += sizeof(jobs_key) + int_decimal_len;
len += sizeof(priority_key) + int_decimal_len;
sdks = dcc_xci_scan_developer_sdks();
if (sdks) {
for (sdk = sdks; *sdk; ++sdk)
len += sizeof(sdk_key) + strlen(*sdk);
}
++len;
info = malloc(len);
if (!info) {
rs_log_error("malloc(%d) failed: %s", len, strerror(errno));
goto out_error;
}
info[pos] = '\0';
if (sys) {
pos += snprintf(info + pos, len - pos, "%s%s\n", sys_key, sys);
if (pos >= len)
goto out_error_info_size;
}
pos += snprintf(info + pos, len - pos, "%s\n", distcc_key_and_value);
if (pos >= len)
goto out_error_info_size;
if (compilers) {
for (compiler = compilers; *compiler; ++compiler) {
pos += snprintf(info + pos, len - pos, "%s%s\n",
compiler_key, *compiler);
if (pos >= len)
goto out_error_info_size;
}
free(compilers);
compilers = NULL;
}
if (dcc_ncpus(&ncpus) == 0) {
pos += snprintf(info + pos, len - pos, "%s%d\n", cpus_key, ncpus);
if (pos >= len)
goto out_error_info_size;
}
if (dcc_cpuspeed(&cpuspeed) == 0) {
pos += snprintf(info + pos, len - pos, "%s%llu\n", cpuspeed_key,
cpuspeed);
if (pos >= len)
goto out_error_info_size;
}
if (dcc_max_kids) {
pos += snprintf(info + pos, len - pos, "%s%d\n",
jobs_key, dcc_max_kids);
if (pos >= len)
goto out_error_info_size;
}
pos += snprintf(info + pos, len - pos, "%s%d\n", priority_key,
arg_priority);
if (pos >= len)
goto out_error_info_size;
if (sdks) {
for (sdk = sdks; *sdk; ++sdk) {
pos += snprintf(info + pos, len - pos, "%s%s\n", sdk_key, *sdk);
if (pos >= len)
goto out_error_info_size;
}
dcc_free_argv(sdks);
sdks = NULL;
}
if (pos + 1 < len) {
if (!(host_info = realloc(info, pos + 1))) {
rs_log_error("realloc() failed: %s", strerror(errno));
goto out_error;
}
} else {
host_info = info;
}
info = NULL;
return host_info;
out_error_info_size:
rs_log_error("info buffer of size %d is too small", len);
out_error:
if (info)
free(info);
if (compilers)
free(compilers);
if (sdks)
dcc_free_argv(sdks);
return NULL;
}
#endif