#if defined(DARWIN)
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dirent.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "bulk.h"
#include "config.h"
#include "exitcode.h"
#include "indirect_server.h"
#include "indirect_util.h"
#include "io.h"
#include "rpc.h"
#include "tempfile.h"
#include "trace.h"
static const char message_terminator = '\n';
static int childWrite[2];
static pthread_t indirectionThread = { 0 };
static int parentWrite[2];
int sys_lock(int fd, int block);
static int read_from_child(char *buffer, const int size)
{
int idx = 0;
int result;
if ( size <= 0 ) {
return 0;
}
do {
result = read(childWrite[0], &buffer[idx], 1);
if ( result <= 0 || idx >= size ) {
return 0;
} else {
idx += result;
}
} while ( buffer[idx - 1] != message_terminator );
buffer[idx - 1] = '\0';
return 1;
}
static int write_to_child(const char *message)
{
int result;
if ( message ) {
const int length = strlen(message);
int idx = 0;
while ( idx < length ) {
result = write(parentWrite[1], &message[idx], length - idx);
if ( result < 0 ) {
return 0;
} else {
idx += result;
}
}
}
result = write(parentWrite[1], &message_terminator, 1);
if ( result < 0 ) {
return 0;
}
return 1;
}
static int mkdir_p(const char *path_to_dir)
{
char *path = strdup(path_to_dir);
char current_path[MAXPATHLEN];
char *latest_path_element;
current_path[0] = '\0';
latest_path_element = strtok(path, "/");
while ( strlen(current_path) < MAXPATHLEN - MAXNAMLEN - 1 ) {
if ( latest_path_element == NULL ) {
break;
} else {
strcat(current_path, "/");
strcat(current_path, latest_path_element);
if ( mkdir(current_path, 504) == 0 ) {
rs_trace("Created directory %s", current_path);
} else {
if ( errno == EEXIST ) {
rs_trace("Directory exists: %s", current_path);
} else {
rs_log_error("Unable to create directory %s: %s",
current_path, strerror(errno));
return 0;
}
}
latest_path_element = strtok(NULL, "/");
}
}
free(path);
if ( latest_path_element == NULL ) {
return 1;
} else {
return 0;
}
}
static char *dcc_create_checksum_tmpfile(const char *tmp_path,
const char *checksum)
{
char *checksum_tmpfile = malloc(MAXPATHLEN);
int fd;
if ( checksum_tmpfile == NULL ) {
rs_log_error("Unable to allocate space for checksum_tmpfile");
return NULL;
}
strncpy(checksum_tmpfile, tmp_path, MAXPATHLEN);
strcat(checksum_tmpfile, checksum_suffix);
fd = open(checksum_tmpfile, O_WRONLY|O_CREAT, 0777);
if ( fd < 0 ) {
rs_log_error("Unable to create %s", checksum_tmpfile);
return NULL;
} else {
size_t checksum_length = strlen(checksum);
if ( write(fd, checksum, checksum_length) == (ssize_t)checksum_length ){
rs_trace("Wrote checksum to %s", checksum_tmpfile);
close(fd);
return checksum_tmpfile;
} else {
rs_log_error("Unable to write checksum to %s", checksum_tmpfile);
close(fd);
remove(checksum_tmpfile);
return NULL;
}
}
}
static char *dcc_pull_checksum(int netfd, const char *tmp_path)
{
int checksum_length = 0;
if ( dcc_r_token_int(netfd, checksum_length_token, &checksum_length)
|| checksum_length <= 0 ) {
rs_log_error("No incoming checksum for path %s", tmp_path);
return NULL;
} else {
char *checksum = malloc(checksum_length + 1);
if ( dcc_readx(netfd, checksum, checksum_length) ) {
rs_log_error("Unable to read checksum for path %s", tmp_path);
free(checksum);
return NULL;
} else {
char *tmp_checksum_filename = dcc_create_checksum_tmpfile(tmp_path,
checksum);
free(checksum);
return tmp_checksum_filename;
}
}
}
static int dcc_push_checksum(int netfd, const char *hostname,
const char *checksum)
{
size_t checksum_length = ( checksum == NULL ) ? 0 : strlen(checksum) + 1;
if ( dcc_x_token_int(netfd, checksum_length_token, checksum_length) ) {
rs_log_error("Unable to transmit checksum length %u to %s",
checksum_length, hostname);
return 0;
} else if ( checksum_length > 0 ) {
if ( dcc_writex(netfd, checksum, checksum_length) ) {
rs_log_error("Unable to transmit checksum \"%s\" to %s",
checksum, hostname);
return 0;
}
}
return 1;
}
static char *dcc_read_checksum(const char *cached_path_checksum,
size_t cached_path_checksum_size)
{
int remaining_buffer = cached_path_checksum_size + 1;
char *checksum = malloc(remaining_buffer);
if ( checksum == NULL ) {
rs_log_error("Unable to allocate space for checksum_tmpfile");
} else {
int checksum_file = open(cached_path_checksum, O_RDONLY, 0777);
if ( checksum_file <= 0 ) {
rs_log_error("Unable to open %s", cached_path_checksum);
free(checksum);
checksum = NULL;
} else {
int num_read = -1;
while ( remaining_buffer > 0 &&
( num_read = read(checksum_file, checksum,
remaining_buffer) ) > 0 ) {
remaining_buffer -= num_read;
}
checksum[cached_path_checksum_size] = '\0';
if ( num_read < 0 ||
strlen(checksum) < cached_path_checksum_size ) {
rs_log_error("Unable to read checksum from %s",
cached_path_checksum);
free(checksum);
checksum = NULL;
}
close(checksum_file);
}
}
return checksum;
}
static void dcc_pull_directory_file(int netfd, const char *hostname,
const char *tmp_path)
{
int cwd = open(".", O_RDONLY, 0777);
if ( chdir(tmp_path) ) {
rs_log_error("Unable to chdir to %s", tmp_path);
} else {
int incomingBytes;
if ( dcc_r_token_int(netfd, result_name_token, &incomingBytes)
|| incomingBytes <= 0 ) {
rs_log_error("No incoming file from %s for directory %s",
hostname, tmp_path);
} else {
char filename[incomingBytes];
if ( dcc_readx(netfd, filename, incomingBytes) ) {
rs_log_error("Unable to read filename from %s for file in directory %s", hostname, tmp_path);
} else {
if (dcc_r_token_int(netfd, result_item_token, &incomingBytes)) {
rs_log_error("Unable to read length from %s for file %s in %s", hostname, filename, tmp_path);
} else {
if ( dcc_r_file_timed(netfd, filename, incomingBytes) ) {
rs_log_error("Failed to retrieve from %s incoming file %s/%s", hostname, tmp_path, filename);
} else {
rs_log_info("Stored incoming file from %s at %s in %s",
hostname, filename, tmp_path);
}
}
}
}
}
fchdir(cwd);
close(cwd);
}
static int dcc_rmdir(const char *path) {
int numFiles;
char **filenames = dcc_filenames_in_directory(path, &numFiles);
if ( filenames != NULL ) {
int dir = open(path, O_RDONLY, 0777);
if ( dir > 0 ) {
int cwd = open(".", O_RDONLY, 0777);
if ( fchdir(dir) == 0 ) {
int i;
for ( i = 0; i < numFiles && filenames[i] != NULL; i++ ) {
if ( unlink(filenames[i]) ) {
rs_log_error("Unable to remove %s from %s",
filenames[i], path);
} else {
rs_trace("Removed %s from %s", filenames[i], path);
}
free(filenames[i]);
}
}
fchdir(cwd);
close(cwd);
close(dir);
}
free(filenames);
}
return rmdir(path);
}
#if ! defined(HAVE_FLOCK)
static int sys_shlock(int fd, int block)
{
#if defined(F_SETLK) && ! defined(DARWIN)
struct flock lockparam;
lockparam.l_type = F_RDLCK;
lockparam.l_whence = SEEK_SET;
lockparam.l_start = 0;
lockparam.l_len = 0;
return fcntl(fd, block ? F_SETLKW : F_SETLK, &lockparam);
#elif defined(HAVE_FLOCK)
return flock(fd, LOCK_SH | (block ? 0 : LOCK_NB));
#else
# error "No supported lock method. Please port this code."
#endif
}
#endif // ! defined(HAVE_FLOCK)
static int dcc_shared_lock_on_checksum_file(const char *checksum_path)
{
int fd;
rs_trace("Taking read lock on %s", checksum_path);
#if defined(HAVE_FLOCK)
fd = open(checksum_path, O_RDONLY | O_SHLOCK, 0777);
#else
fd = open(checksum_path, O_RDONLY, 0777);
sys_shlock(fd, 1);
#endif
rs_trace("Got read lock (released automatically on process exit) on %s",
checksum_path);
return fd;
}
static int dcc_exclusive_lock_on_checksum_file(const char *checksum_path)
{
int fd = -1;
rs_trace("Taking a write lock on %s", checksum_path);
#if defined(HAVE_FLOCK)
fd = open(checksum_path, O_WRONLY|O_CREAT|O_EXLOCK, 0777);
#else
fd = open(checksum_path, O_WRONLY|O_CREAT, 0777);
sys_lock(fd, 1);
#endif
rs_trace("Got write lock on %s", checksum_path);
return fd;
}
static int dcc_really_unlock(int lock_fd)
{
#if defined(F_SETLK) && ! defined(DARWIN)
struct flock lockparam;
lockparam.l_type = F_UNLCK;
lockparam.l_whence = SEEK_SET;
lockparam.l_start = 0;
lockparam.l_len = 0;
if (fcntl(lock_fd, F_SETLK, &lockparam))
#elif defined(HAVE_FLOCK)
if (flock(lock_fd, LOCK_UN))
#elif defined(HAVE_LOCKF)
if (lockf(lock_fd, F_ULOCK, 0))
#else
# error "No supported lock method. Please port this code."
#endif
{
rs_log_error("unlock failed: %s", strerror(errno));
return EXIT_IO_ERROR;
}
rs_trace("really released lock");
return 0;
}
static void dcc_unlock_checksum_file(int fd, const char *checksum_path)
{
dcc_really_unlock(fd);
rs_trace("Released write lock on %s", checksum_path);
if ( fd >= 0 ) {
close(fd);
}
}
static void dcc_handle_pull(int netfd, const char *hostname)
{
char *actual_path = NULL;
size_t hostname_length = strlen(hostname) + 1;
char response[MAXPATHLEN];
const char *tempdir = dcc_get_tempdir();
size_t tempdir_length = strlen(tempdir) + 1;
int fd;
if ( read_from_child(response, MAXPATHLEN) ) {
size_t response_length = strlen(response) + 1;
size_t total_length = tempdir_length + hostname_length
+ response_length;
char *cached_path = malloc(total_length);
char *cached_path_checksum = malloc(total_length
+ checksum_suffix_length);
int cached_path_exists;
int cached_path_checksum_exists;
size_t cached_path_checksum_size;
char *checksum = NULL;
struct stat sb;
rs_trace("Attempting to pull %s", response);
strcat(cached_path, tempdir);
strcat(cached_path, "/");
strcat(cached_path, hostname);
strcat(cached_path, "/");
strcat(cached_path, response);
strncpy(cached_path_checksum, cached_path, total_length);
strncat(cached_path_checksum, checksum_suffix, checksum_suffix_length);
cached_path_exists = ( stat(cached_path, &sb) == 0 );
cached_path_checksum_exists = ( stat(cached_path_checksum, &sb) == 0 );
cached_path_checksum_size = sb.st_size;
fd = dcc_shared_lock_on_checksum_file(cached_path_checksum);
if ( cached_path_exists && cached_path_checksum_exists &&
cached_path_checksum_size > 0 ) {
checksum = dcc_read_checksum(cached_path_checksum,
cached_path_checksum_size);
}
if ( dcc_x_token_int(netfd, indirection_request_token,
indirection_request_pull)
|| dcc_x_token_int(netfd, operation_pull_token, response_length)
|| dcc_writex(netfd, response, response_length) ) {
rs_log_error("Unable to transmit filename to %s", hostname);
actual_path = response;
} else {
if ( ! dcc_push_checksum(netfd, hostname, checksum) ) {
actual_path = response;
}
}
dcc_unlock_checksum_file(fd, cached_path_checksum);
if ( checksum != NULL ) {
free(checksum);
checksum = NULL;
}
if ( actual_path == NULL ) {
int result_type = result_type_nothing;
if ( dcc_r_token_int(netfd, result_type_token, &result_type) ) {
rs_log_error("Unable to read result type from %s", hostname);
actual_path = response;
} else if ( result_type == result_type_nothing ||
result_type == result_type_checksum_only ) {
if ( cached_path_exists && cached_path_checksum_exists ) {
rs_log_info("Using cached version: %s", cached_path);
actual_path = cached_path;
} else {
rs_log_error("Unable to find %s on %s", response, hostname);
actual_path = response;
}
if ( result_type == result_type_checksum_only ) {
char *tmpName = dcc_make_tmpnam(hostname, response);
char *tmpChecksumName = NULL;
char *lastSlash = strrchr(tmpName, '/');
lastSlash[0] = '\0';
if ( mkdir_p(tmpName) ) {
lastSlash[0] = '/';
tmpChecksumName = dcc_pull_checksum(netfd, tmpName);
if ( tmpChecksumName != NULL ) {
int xfd = dcc_exclusive_lock_on_checksum_file(cached_path_checksum);
if ( rename(tmpChecksumName, cached_path_checksum)){
rs_log_error("Failed to move %s to %s: %s",
tmpChecksumName,
cached_path_checksum,
strerror(errno));
} else {
rs_trace("Moved %s to %s", tmpChecksumName,
cached_path_checksum);
}
dcc_unlock_checksum_file(xfd, cached_path_checksum);
free(tmpChecksumName);
}
} else {
rs_log_error("Unable to create directory %s", tmpName);
}
free(tmpName);
free(cached_path_checksum);
}
} else if ( result_type == result_type_file ) {
char *tmpChecksumName = NULL;
char *tmpName = dcc_make_tmpnam(hostname, response);
char *lastSlash = strrchr(tmpName, '/');
int incomingBytes;
if ( dcc_r_token_int(netfd, result_item_token, &incomingBytes)
|| incomingBytes <= 0 ) {
rs_log_error("No incoming file for path %s", response);
actual_path = response;
} else {
lastSlash[0] = '\0';
if ( mkdir_p(tmpName) ) {
lastSlash[0] = '/';
if ( dcc_r_file_timed(netfd, tmpName, incomingBytes)
== 0 ) {
rs_log_info("Stored incoming file at %s", tmpName);
tmpChecksumName = dcc_pull_checksum(netfd, tmpName);
} else {
rs_log_error("Failed to retrieve incoming file %s",
tmpName);
actual_path = response;
}
} else {
rs_log_error("Unable to create directory %s", tmpName);
actual_path = response;
}
}
if ( actual_path == NULL ) {
int xfd;
lastSlash = strrchr(cached_path, '/');
lastSlash[0] = '\0';
if ( ! mkdir_p(cached_path) ) {
rs_log_error("Unable to create directory %s",
cached_path);
}
lastSlash[0] = '/';
xfd = dcc_exclusive_lock_on_checksum_file(cached_path_checksum);
if ( rename(tmpName, cached_path) ) {
rs_log_error("Failed to move %s to %s: %s", tmpName,
cached_path, strerror(errno));
actual_path = response;
} else {
rs_trace("Moved %s to %s", tmpName, cached_path);
actual_path = cached_path;
if ( tmpChecksumName != NULL ) {
if ( rename(tmpChecksumName, cached_path_checksum)){
rs_log_error("Failed to move %s to %s: %s",
tmpChecksumName,
cached_path_checksum,
strerror(errno));
} else {
rs_trace("Moved %s to %s", tmpChecksumName,
cached_path_checksum);
}
}
}
dcc_unlock_checksum_file(xfd, cached_path_checksum);
}
free(cached_path_checksum);
if ( tmpChecksumName != NULL ) {
free(tmpChecksumName);
}
free(tmpName);
} else if ( result_type == result_type_dir ) {
char *tmpChecksumName = NULL;
char *tmpName = dcc_make_tmpnam(hostname, response);
int fileCount;
if ( dcc_r_token_int(netfd, result_count_token, &fileCount)
|| fileCount <= 0 ) {
rs_log_error("No incoming files for path %s", response);
actual_path = response;
} else {
if ( mkdir_p(tmpName) ) {
int i;
for ( i = 0; i < fileCount; i++ ) {
dcc_pull_directory_file(netfd, hostname, tmpName);
}
tmpChecksumName = dcc_pull_checksum(netfd, tmpName);
} else {
rs_log_error("Unable to create directory %s", tmpName);
actual_path = response;
}
}
if ( actual_path == NULL ) {
int xfd;
if ( dcc_rmdir(cached_path) ) {
char *lastSlash = strrchr(cached_path, '/');
rs_log_error("Unable to remove %s", cached_path);
lastSlash[0] = '\0';
if ( ! mkdir_p(cached_path) ) {
rs_log_error("Unable to create directory %s",
cached_path);
actual_path = response;
}
lastSlash[0] = '/';
}
xfd = dcc_exclusive_lock_on_checksum_file(cached_path_checksum);
if ( rename(tmpName, cached_path) ) {
rs_log_error("Failed to move %s to %s: %s", tmpName,
cached_path, strerror(errno));
actual_path = response;
} else {
rs_trace("Moved %s to %s", tmpName, cached_path);
actual_path = cached_path;
if ( tmpChecksumName != NULL ) {
if ( rename(tmpChecksumName, cached_path_checksum)){
rs_log_error("Failed to move %s to %s: %s",
tmpChecksumName,
cached_path_checksum,
strerror(errno));
} else {
rs_trace("Moved %s to %s", tmpChecksumName,
cached_path_checksum);
}
}
}
dcc_unlock_checksum_file(xfd, cached_path_checksum);
}
free(cached_path_checksum);
if ( tmpChecksumName != NULL ) {
free(tmpChecksumName);
}
free(tmpName);
}
}
fd = dcc_shared_lock_on_checksum_file(cached_path_checksum);
} else {
rs_log_error("Unable to receive %s request", operation_pull_token);
actual_path = (char *) "UNKNOWN";
}
if ( write_to_child(actual_path) ) {
rs_log_info("Using %s", actual_path);
} else {
rs_log_error("Unable to contact child for path %s", actual_path);
}
if ( actual_path != response ) {
free(actual_path);
}
}
static void *dcc_handle_indirection(void *ifd)
{
int netfd = *((int *)ifd);
char *hostname = (char *) "unknown";
struct sockaddr_in sain;
size_t sain_length = sizeof(sain);
size_t actual_length = sain_length;
if ( getpeername(netfd, (struct sockaddr *) &sain, (int *) &actual_length)
== 0 && actual_length <= sain_length ) {
hostname = inet_ntoa(sain.sin_addr);
} else {
rs_log_error("Unable to determine remote hostname; using \"unknown\"");
}
while (1) {
char response[10];
if ( read_from_child(response, 10) ) {
if ( strcmp(operation_pull_token, response) == 0 ) {
dcc_handle_pull(netfd, hostname);
} else if ( strcmp(operation_push_token, response) == 0 ) {
rs_log_error("Unsupported operation: %s", response);
} else if ( strcmp(operation_both_token, response) == 0 ) {
rs_log_error("Unsupported operation: %s", response);
} else if ( strcmp(operation_version_token, response) == 0 ) {
if ( read_from_child(response, 10) ) {
if ( strcmp(indirection_protocol_version, response) == 0 ) {
write_to_child("OK");
continue;
}
}
write_to_child("NO");
} else {
rs_log_error("Unsupported operation: %s", response);
}
}
}
}
void dcc_close_pipe_end_child(void)
{
close(parentWrite[1]);
close(childWrite[0]);
}
void dcc_close_pipe_end_parent(void)
{
close(parentWrite[0]);
close(childWrite[1]);
}
void dcc_support_indirection(int *ifd)
{
char *fdString;
int result = pipe(parentWrite);
if ( result != 0 ) {
rs_log_error("Unable to create file indirection pipe set #1: %s", strerror(errno));
return;
}
result = pipe(childWrite);
if ( result != 0 ) {
rs_log_error("Unable to create file indirection pipe set #2: %s", strerror(errno));
close(parentWrite[0]);
close(parentWrite[1]);
return;
}
result = asprintf(&fdString, "%d, %d", parentWrite[0], childWrite[1]);
if ( result <= 0 ) {
rs_log_error("Unable to create file indirection pipe description: %s", strerror(errno));
dcc_close_pipe_end_child();
dcc_close_pipe_end_parent();
return;
}
result = setenv("GCC_INDIRECT_FILES", fdString, 1);
if ( result != 0 ) {
rs_log_error("Unable to set indirection environment variable");
dcc_close_pipe_end_child();
dcc_close_pipe_end_parent();
free(fdString);
return;
}
result = pthread_create(&indirectionThread, NULL, dcc_handle_indirection, ifd);
if ( result == 0 ) {
rs_log_info("Created thread to handle file indirection");
} else {
rs_log_error("Unable to create thread to handle file indirection");
dcc_close_pipe_end_child();
dcc_close_pipe_end_parent();
free(fdString);
unsetenv("GCC_INDIRECT_FILES");
return;
}
}
#endif // DARWIN