#if defined(DARWIN)
#include <dns_sd.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include "config.h"
#include "distcc.h"
#include "exitcode.h"
#include "trace.h"
#include "zeroconf_browse.h"
#include "zeroconf_util.h"
#define ADD_SERVICE_COUNT 16
#define MAX_INTERFACES 16
#define SERVICE_EMPTY_STRING ""
#define SERVICE_TRAILING_SPACE " "
#define ZC_BROWSE_FLAGS 0
#define ZC_RESOLVE_FLAGS 0
typedef struct serviceEntry {
char *description;
char *domain;
char *fullName;
char *hostTarget;
uint32_t interfaceIndex[MAX_INTERFACES];
char *name;
uint16_t port;
int refCount;
char *regType;
DNSServiceRef resolveRef;
int resolveRefSockFD;
uint16_t txtLen;
char *txtRecord;
} ServiceEntry;
typedef struct serviceEntryTable {
size_t capacity;
ServiceEntry **services;
} ServiceEntryTable;
static const size_t SERVICE_ENTRY_SIZE = sizeof(ServiceEntry);
static const size_t SERVICE_ENTRY_PTR_SIZE = sizeof(ServiceEntry *);
static int zcBrowseFD;
static DNSServiceRef zcBrowseRef = NULL;
static int zcMoreComing = FALSE;
static char *zcResolvedSvcs = NULL;
static int zcSelectFDCount = 0;
static fd_set zcSelectFDs;
static ServiceEntryTable zcServices = { 0, NULL };
static void dcc_zc_free_service_entry(ServiceEntry **aServiceEntry)
{
if ( (*aServiceEntry)->description != NULL ) {
free((*aServiceEntry)->description);
}
if ( (*aServiceEntry)->domain != NULL ) {
free((*aServiceEntry)->domain);
}
if ( (*aServiceEntry)->fullName != NULL ) {
free((*aServiceEntry)->fullName);
}
if ( (*aServiceEntry)->hostTarget != NULL ) {
free((*aServiceEntry)->hostTarget);
}
if ( (*aServiceEntry)->name != NULL ) {
free((*aServiceEntry)->name);
}
if ( (*aServiceEntry)->regType != NULL ) {
free((*aServiceEntry)->regType);
}
if ( (*aServiceEntry)->resolveRef != NULL ) {
dcc_zc_remove_from_select_set((*aServiceEntry)->resolveRefSockFD);
DNSServiceRefDeallocate((*aServiceEntry)->resolveRef);
}
if ( (*aServiceEntry)->txtRecord != NULL ) {
free((*aServiceEntry)->txtRecord);
}
free(*aServiceEntry);
*aServiceEntry = NULL;
}
static void dcc_zc_determine_select_count(void)
{
int i;
for ( i = 0; i < FD_SETSIZE; i++ ) {
if ( FD_ISSET(i, &zcSelectFDs) ) {
zcSelectFDCount = i;
}
}
zcSelectFDCount++;
}
void dcc_zc_add_to_select_set(int anFD)
{
if ( anFD > 0 && anFD < FD_SETSIZE ) {
if ( FD_ISSET(anFD, &zcSelectFDs) ) {
rs_trace("Already included file descriptor: %d", anFD);
} else {
FD_SET(anFD, &zcSelectFDs);
dcc_zc_determine_select_count();
}
} else {
rs_trace("Unable to include invalid file descriptor: %d", anFD);
}
}
void dcc_zc_remove_from_select_set(int anFD)
{
if ( anFD > 0 && anFD < FD_SETSIZE ) {
if ( FD_ISSET(anFD, &zcSelectFDs) ) {
FD_CLR(anFD, &zcSelectFDs);
dcc_zc_determine_select_count();
} else {
rs_trace("Already excluded file descriptor: %d", anFD);
}
} else {
rs_trace("Unable to exclude invalid file descriptor: %d", anFD);
}
}
int dcc_zc_select_count(void)
{
return zcSelectFDCount;
}
void dcc_zc_select_set(fd_set *setCopy)
{
FD_COPY(&zcSelectFDs, setCopy);
}
int dcc_zc_process_browse_messages(fd_set *availableFDs, int fdCount)
{
if ( FD_ISSET(zcBrowseFD, availableFDs) ) {
DNSServiceErrorType exitVal = DNSServiceProcessResult(zcBrowseRef);
if ( exitVal != kDNSServiceErr_NoError ) {
rs_log_error("Failure processing zeroconfiguration browse messages: %d", exitVal);
}
return ( fdCount - 1 );
} else {
return fdCount;
}
}
int dcc_zc_process_resolve_messages(fd_set *availableFDs, int fdCount)
{
size_t capacity = zcServices.capacity;
size_t i;
int newFDCount = fdCount;
ServiceEntry **services = zcServices.services;
for ( i = 0; i < capacity && newFDCount > 0; i++ ) {
ServiceEntry *service = services[i];
if ( service != NULL ) {
int resolveFD = service->resolveRefSockFD;
if ( resolveFD > 0 && FD_ISSET(resolveFD, availableFDs) ) {
DNSServiceErrorType exitVal = DNSServiceProcessResult(service->resolveRef);
if ( exitVal != kDNSServiceErr_NoError ) {
rs_log_error("Failure processing zeroconfiguration resolve messages: %d", exitVal);
}
newFDCount--;
}
}
}
return newFDCount;
}
int dcc_zc_should_process_client_requests(void)
{
return ( ! zcMoreComing );
}
char *dcc_zc_resolved_services_list(void)
{
if ( zcResolvedSvcs == NULL ) {
return (char *) "localhost";
} else {
return zcResolvedSvcs;
}
}
static void dcc_zc_build_resolved_services_list(void)
{
size_t capacity = zcServices.capacity;
size_t i;
size_t listLength = 0;
char *oldList = zcResolvedSvcs;
ServiceEntry **services = zcServices.services;
for ( i = 0; i < capacity; i++ ) {
ServiceEntry *service = services[i];
if ( service != NULL && service->description != NULL ) {
listLength += strlen(service->description) + 1;
}
}
if ( listLength > 0 ) {
char *serviceList = malloc(listLength);
if ( serviceList != NULL ) {
for ( i = 0; i < capacity; i++ ) {
ServiceEntry *service = services[i];
if ( service != NULL && service->description != NULL ) {
strcat(serviceList, service->description);
strcat(serviceList, " ");
}
}
serviceList[listLength-1] = '\0';
zcResolvedSvcs = serviceList;
rs_trace("Resolved distcc services are: %s", zcResolvedSvcs);
}
} else {
zcResolvedSvcs = NULL;
}
if ( oldList != NULL ) {
free(oldList);
}
}
static void dcc_zc_service_resolved(const DNSServiceRef ref,
uint32_t anInterfaceIndex,
const char *aFullName,
const char *aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const char *aTxtRecord)
{
size_t capacity = zcServices.capacity;
size_t i;
ServiceEntry *service = NULL;
ServiceEntry **serviceList = zcServices.services;
for ( i = 0; i < capacity; i++ ) {
service = serviceList[i];
if ( service != NULL && service->resolveRef == ref ) {
break;
}
}
if ( i == capacity ) {
rs_log_error("Cannot find resolved service: \"%s\"", aFullName);
} else {
service->port = aPort;
service->txtLen = aTxtLen;
service->description = malloc(strlen(aHostTarget) + 1 + 5 + 1);
service->fullName = malloc(strlen(aFullName) + 1);
service->hostTarget = malloc(strlen(aHostTarget) + 1);
service->txtRecord = malloc(strlen(aTxtRecord) + 1);
if ( service->description == NULL || service->fullName == NULL ||
service->hostTarget == NULL || service->txtRecord == NULL ) {
rs_log_error("Unable to allocate space for data for resolved service \"%s\" on interface %d", aFullName, anInterfaceIndex);
dcc_zc_free_service_entry(&serviceList[i]);
} else {
sprintf(service->description, "%s:%u", aHostTarget, aPort);
strcpy(service->fullName, aFullName);
strcpy(service->hostTarget, aHostTarget);
strcpy(service->txtRecord, aTxtRecord);
}
}
}
static void dcc_zc_service_unresolved(const DNSServiceRef ref,
uint32_t anInterfaceIndex,
const char *aFullName,
const char * UNUSED(aHostTarget),
uint16_t UNUSED(aPort),
uint16_t UNUSED(aTxtLen),
const char * UNUSED(aTxtRecord))
{
size_t capacity = zcServices.capacity;
size_t i;
ServiceEntry **serviceList = zcServices.services;
for ( i = 0; i < capacity; i++ ) {
ServiceEntry *service = serviceList[i];
if ( service != NULL && service->resolveRef == ref ) {
if ( service->resolveRef != NULL ) {
dcc_zc_remove_from_select_set(service->resolveRefSockFD);
DNSServiceRefDeallocate(service->resolveRef);
service->resolveRef = NULL;
service->resolveRefSockFD = 0;
rs_trace("Stopped resolving service \"%s\" on interface %d",
aFullName, anInterfaceIndex);
}
break;
}
}
if ( i == capacity ) {
rs_log_error("Cannot find service \"%s\" on interface %d to stop resolution", aFullName, anInterfaceIndex);
}
}
static void dcc_zc_resolve_reply(const DNSServiceRef ref,
DNSServiceFlags UNUSED(flags),
uint32_t anInterfaceIndex,
DNSServiceErrorType errorCode,
const char *aFullName,
const char *aHostTarget,
uint16_t aPort,
uint16_t aTxtLen,
const char *aTxtRecord,
void *context)
{
int matchedProfile = ( strncmp((char *) context, aTxtRecord,
strlen((char *) context)) == 0 );
if ( errorCode ) {
rs_log_error("Encountered zeroconfiguration resolution error: %d",
errorCode);
}
if ( ! matchedProfile ) {
rs_log_info("Distributed compile profile did not match for \"%s\"",
aFullName);
dcc_zc_service_unresolved(ref, anInterfaceIndex, aFullName, aHostTarget,
aPort, aTxtLen, aTxtRecord);
} else {
dcc_zc_service_resolved(ref, anInterfaceIndex, aFullName, aHostTarget,
aPort, aTxtLen, aTxtRecord);
dcc_zc_build_resolved_services_list();
}
}
static size_t dcc_zc_expand_service_table(void)
{
size_t currentCapacity = zcServices.capacity;
size_t j;
size_t newCapacity = currentCapacity + ADD_SERVICE_COUNT;
rs_trace("Increasing service list capacity to %lu", newCapacity);
zcServices.services = realloc(zcServices.services,
newCapacity * SERVICE_ENTRY_PTR_SIZE);
if ( zcServices.services == NULL ) {
rs_log_error("Unable to increase the service list capacity");
newCapacity = -1;
} else {
for ( j = currentCapacity; j < newCapacity; j++ ) {
(zcServices.services)[j] = NULL;
}
zcServices.capacity = newCapacity;
}
return newCapacity;
}
static void dcc_zc_add_service(const char *aName, const char *aRegType,
const char *aDomain,
const uint32_t anInterfaceIndex,
void *clientTxtRecord)
{
size_t capacity = zcServices.capacity;
char *fullName = dcc_zc_full_name(aName, aRegType, aDomain);
size_t i;
ServiceEntry **serviceList = zcServices.services;
for ( i = 0; i < capacity; i++ ) {
ServiceEntry *service = serviceList[i];
if ( service != NULL && strcmp(service->name, aName) == 0 ) {
rs_log_info("Duplicate registration of service \"%s\" on interface %d", fullName, anInterfaceIndex);
if ( service->refCount == MAX_INTERFACES - 1 ) {
rs_log_info("Too many duplicates to record; ignoring");
} else {
int refCount;
rs_log_info("Recorded duplicate");
refCount = service->refCount++;
(service->interfaceIndex)[refCount] = anInterfaceIndex;
}
free(fullName);
return;
}
}
for ( i = 0; i < capacity; i++ ) {
ServiceEntry *service = serviceList[i];
if ( service == NULL ) {
break;
}
}
if ( i == capacity ) {
capacity = dcc_zc_expand_service_table();
serviceList = zcServices.services;
}
if ( i < capacity ) {
ServiceEntry *newService = malloc(SERVICE_ENTRY_SIZE);
if ( newService == NULL ) {
rs_log_error("Unable to allocate space for new service \"%s\" on interface %d", fullName, anInterfaceIndex);
} else {
DNSServiceErrorType exitValue;
int failedToInitialize = FALSE;
newService->description = NULL;
newService->fullName = NULL;
newService->hostTarget = NULL;
newService->port = 0;
newService->txtLen = 0;
newService->txtRecord = NULL;
newService->refCount = 0;
(newService->interfaceIndex)[0] = anInterfaceIndex;
newService->name = malloc(strlen(aName) + 1);
newService->regType = malloc(strlen(aRegType) + 1);
newService->domain = malloc(strlen(aDomain) + 1);
exitValue = DNSServiceResolve(&(newService->resolveRef),
ZC_RESOLVE_FLAGS, anInterfaceIndex,
aName, aRegType, aDomain,
(DNSServiceResolveReply)dcc_zc_resolve_reply,
clientTxtRecord);
if ( exitValue != kDNSServiceErr_NoError ||
newService->resolveRef == NULL ) {
rs_log_error("Failed to resolve new service...");
failedToInitialize = TRUE;
} else {
newService->resolveRefSockFD = DNSServiceRefSockFD(newService->resolveRef);
if ( newService->resolveRefSockFD < 0 ||
newService->resolveRefSockFD > FD_SETSIZE ) {
rs_log_error("Failed to determine resolve socket...");
failedToInitialize = TRUE;
} else {
dcc_zc_add_to_select_set(newService->resolveRefSockFD);
}
}
if ( newService->name == NULL || newService->regType == NULL ||
newService->domain == NULL ) {
rs_log_error("Unable to allocate space for data for new service...");
} else {
strcpy(newService->name, aName);
strcpy(newService->regType, aRegType);
strcpy(newService->domain, aDomain);
}
if ( failedToInitialize ) {
rs_log_error("...unable to add service \"%s\" on interface %d",
fullName, anInterfaceIndex);
dcc_zc_free_service_entry(&newService);
}
}
if ( newService != NULL ) {
serviceList[i] = newService;
rs_log_info("Added service \"%s\" on interface %d", fullName,
anInterfaceIndex);
}
} else {
rs_log_error("Unable to add service \"%s\" on interface %d; cannot resize list", fullName, anInterfaceIndex);
}
free(fullName);
}
static void dcc_zc_remove_service(const char *aName, const char *aRegType,
const char *aDomain,
const uint32_t anInterfaceIndex,
void *clientTxtRecord)
{
size_t capacity = zcServices.capacity;
char *fullName = dcc_zc_full_name(aName, aRegType, aDomain);
size_t i;
ServiceEntry **serviceList = zcServices.services;
for ( i = 0; i < capacity; i++ ) {
ServiceEntry *service = serviceList[i];
if ( service != NULL && strcmp(aName, service->name) == 0 ) {
if ( service->refCount > 0 ) {
uint32_t *interfaceIndex = service->interfaceIndex;
int k;
int tmpRefCount = service->refCount;
for ( k = 0; k <= tmpRefCount; k++ ) {
if ( interfaceIndex[k] == anInterfaceIndex ) {
break;
}
}
if ( k > tmpRefCount ) {
rs_log_error("Cannot remove duplicate service \"%s\"; interface %d unregistered", fullName, anInterfaceIndex);
} else {
if ( k < tmpRefCount ) {
int j;
for ( j = k + 1; j <= tmpRefCount; j++ ) {
interfaceIndex[j-1] = interfaceIndex[j];
}
}
service->refCount--;
if ( k == 0 ) {
DNSServiceErrorType exitValue;
int failedToResolve = FALSE;
DNSServiceRefDeallocate(service->resolveRef);
exitValue = DNSServiceResolve(&(service->resolveRef),
ZC_RESOLVE_FLAGS,
interfaceIndex[0],
aName, aRegType, aDomain,
(DNSServiceResolveReply)dcc_zc_resolve_reply,
clientTxtRecord);
if ( exitValue != kDNSServiceErr_NoError ||
service->resolveRef == NULL ) {
rs_log_error("Failed to resolve...");
failedToResolve = TRUE;
} else {
service->resolveRefSockFD = DNSServiceRefSockFD(service->resolveRef);
if ( service->resolveRefSockFD < 0 ||
service->resolveRefSockFD > FD_SETSIZE ) {
rs_log_error("Failed to determine resolve socket...");
failedToResolve = TRUE;
}
}
if ( failedToResolve ) {
dcc_zc_free_service_entry(&(serviceList[i]));
rs_log_error("...removed service \"%s\" entirely.",
fullName);
}
}
rs_log_info("Removed duplicate service \"%s\" on interface %d", fullName, anInterfaceIndex);
}
} else {
dcc_zc_free_service_entry(&(serviceList[i]));
rs_log_info("Removed service \"%s\" on interface %d", fullName,
anInterfaceIndex);
}
break;
}
}
if ( i == capacity ) {
rs_log_error("Cannot remove \"%s\" for interface %d; service not found",
fullName, anInterfaceIndex);
}
free(fullName);
}
static void dcc_zc_browse_cleanup(void)
{
size_t i;
if ( zcBrowseRef != NULL ) {
DNSServiceRefDeallocate(zcBrowseRef);
zcBrowseRef = NULL;
}
if ( zcServices.services != NULL ) {
for ( i = 0; i < zcServices.capacity; i++ ) {
if ( zcServices.services[i] != NULL ) {
dcc_zc_free_service_entry(&(zcServices.services[i]));
}
}
free(zcServices.services);
zcServices.services = NULL;
zcServices.capacity = 0;
}
zcSelectFDCount = 0;
FD_ZERO(&zcSelectFDs);
}
static void dcc_zc_browse_reply(const DNSServiceRef UNUSED(ref),
const DNSServiceFlags flags,
const uint32_t anInterfaceIndex,
const DNSServiceErrorType errorCode,
const char *aName,
const char *aRegType,
const char *aDomain,
void *context)
{
if ( errorCode ) {
rs_log_error("Encountered zeroconfiguration browse error: %d",
errorCode);
}
if ( flags & kDNSServiceFlagsMoreComing ) {
zcMoreComing = TRUE;
} else {
zcMoreComing = FALSE;
}
if ( flags & kDNSServiceFlagsAdd ) {
dcc_zc_add_service(aName, aRegType, aDomain, anInterfaceIndex,
context);
} else {
dcc_zc_remove_service(aName, aRegType, aDomain, anInterfaceIndex,
context);
dcc_zc_build_resolved_services_list();
}
}
void dcc_browse_for_zeroconfig(char *txtRecord)
{
int failedToInitialize = FALSE;
FD_ZERO(&zcSelectFDs);
zcServices.capacity = ADD_SERVICE_COUNT;
zcServices.services = calloc(SERVICE_ENTRY_PTR_SIZE, ADD_SERVICE_COUNT);
if ( zcServices.services == NULL ) {
rs_log_error("Unable to allocate memory for service list");
failedToInitialize = TRUE;
} else {
DNSServiceErrorType exitValue = DNSServiceBrowse(&zcBrowseRef,
ZC_BROWSE_FLAGS,
ZC_ALL_INTERFACES,
ZC_REG_TYPE,
ZC_DOMAIN,
(DNSServiceBrowseReply)dcc_zc_browse_reply,
(void *)txtRecord);
if ( exitValue == kDNSServiceErr_NoError && zcBrowseRef != NULL ) {
zcBrowseFD = DNSServiceRefSockFD(zcBrowseRef);
if ( zcBrowseFD < 0 || zcBrowseFD > FD_SETSIZE ) {
rs_log_error("Failed to determine browse socket");
failedToInitialize = TRUE;
} else {
dcc_zc_add_to_select_set(zcBrowseFD);
}
} else {
rs_log_error("Failed to browse for registered distcc daemons");
failedToInitialize = TRUE;
}
}
if ( txtRecord == NULL ) {
rs_log_error("Unable to generate distributed build profile");
failedToInitialize = TRUE;
}
if ( failedToInitialize ) {
rs_fatal("Failed to initialize properly... terminating!");
dcc_zc_browse_cleanup();
exit(EXIT_DISTCC_FAILED);
}
}
#endif // DARWIN