1
/* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*-
3
* Copyright (C) 2007 Lennart Poettering
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public License
7
* as published by the Free Software Foundation; either version 2
8
* of the License, or (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
25
#include <sys/select.h>
37
#include <avahi-common/domain.h>
38
#include <avahi-common/error.h>
39
#include <avahi-common/malloc.h>
40
#include <avahi-common/address.h>
41
#include <avahi-common/simple-watch.h>
42
#include <avahi-client/lookup.h>
50
/* How long shall the background daemon be idle before i terminates itself? */
51
#define MAX_IDLE_TIME 20
53
/* Maxium size of host file to load */
54
#define MAX_FILE_SIZE (1024*100)
56
/* General daemon data */
63
AvahiServiceBrowser *browser;
64
AvahiSimplePoll *simple_poll;
67
/* Zeroconf service wrapper */
69
struct daemon_data *daemon_data;
72
AvahiIfIndex interface;
73
AvahiProtocol protocol;
81
AvahiServiceResolver *resolver;
84
/* A generic, system independant lock routine, similar to sys_lock,
86
* rw: if non-zero: r/w lock instead of r/o lock
87
* enable: lock or unlock
88
* block: block when locking */
89
static int generic_lock(int fd, int rw, int enable, int block) {
91
struct flock lockparam;
93
lockparam.l_type = enable ? (rw ? F_WRLCK : F_RDLCK) : F_UNLCK;
94
lockparam.l_whence = SEEK_SET;
95
lockparam.l_start = 0;
96
lockparam.l_len = 0; /* whole file */
98
return fcntl(fd, block ? F_SETLKW : F_SETLK, &lockparam);
99
#elif defined(HAVE_FLOCK)
100
return flock(fd, (enable ? (rw ? LOCK_EX : LOCK_SH) : LOCK_UN) | (block ? LOCK_NB : 0));
101
#elif defined(HAVE_LOCKF)
102
return lockf(fd, (enable ? (block ? F_LOCK : F_TLOCK) : F_ULOCK));
104
# error "No supported lock method. Please port this code."
108
/* Return the number of seconds, when the specified file was last
109
* read. If the atime of that file is < clip_time, use clip_time
111
static time_t fd_last_used(int fd, time_t clip_time) {
116
if (fstat(fd, &st) < 0) {
117
rs_log_crit("fstat() failed: %s\n", strerror(errno));
121
if ((now = time(NULL)) == (time_t) -1) {
122
rs_log_crit("time() failed: %s\n", strerror(errno));
126
ft = clip_time ? (st.st_atime < clip_time ? clip_time : st.st_atime) : st.st_atime;
132
/* Write host data to host file */
133
static int write_hosts(struct daemon_data *d) {
138
rs_log_info("writing zeroconf data.\n");
140
if (generic_lock(d->fd, 1, 1, 1) < 0) {
141
rs_log_crit("lock failed: %s\n", strerror(errno));
145
if (lseek(d->fd, 0, SEEK_SET) < 0) {
146
rs_log_crit("lseek() failed: %s\n", strerror(errno));
150
if (ftruncate(d->fd, 0) < 0) {
151
rs_log_crit("ftruncate() failed: %s\n", strerror(errno));
155
for (h = d->hosts; h; h = h->next) {
156
char t[256], a[AVAHI_ADDRESS_STR_MAX];
159
/* Not yet fully resolved */
161
if (h->address.proto == AVAHI_PROTO_INET6)
162
snprintf(t, sizeof(t), "[%s]:%u/%i\n", avahi_address_snprint(a, sizeof(a), &h->address), h->port, d->n_slots * h->n_cpus);
164
snprintf(t, sizeof(t), "%s:%u/%i\n", avahi_address_snprint(a, sizeof(a), &h->address), h->port, d->n_slots * h->n_cpus);
166
if (dcc_writex(d->fd, t, strlen(t)) != 0) {
167
rs_log_crit("write() failed: %s\n", strerror(errno));
176
generic_lock(d->fd, 1, 0, 1);
182
static void free_host(struct host *h) {
186
avahi_service_resolver_free(h->resolver);
193
/* Remove a service from the host list */
194
static void remove_service(struct daemon_data *d, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *domain) {
195
struct host *h, *p = NULL;
198
for (h = d->hosts; h; h = h->next) {
199
if (h->interface == interface &&
200
h->protocol == protocol &&
201
!strcmp(h->service, name) &&
202
avahi_domain_equal(h->domain, domain)) {
217
/* Called when a resolve call completes */
218
static void resolve_reply(
219
AvahiServiceResolver *UNUSED(r),
220
AvahiIfIndex UNUSED(interface),
221
AvahiProtocol UNUSED(protocol),
222
AvahiResolverEvent event,
224
const char *UNUSED(type),
225
const char *UNUSED(domain),
226
const char *UNUSED(host_name),
227
const AvahiAddress *a,
229
AvahiStringList *txt,
230
AvahiLookupResultFlags UNUSED(flags),
233
struct host *h = userdata;
237
case AVAHI_RESOLVER_FOUND: {
240
/* Look for the number of CPUs in TXT RRs */
241
for (i = txt; i; i = i->next) {
244
if (avahi_string_list_get_pair(i, &key, &value, NULL) < 0)
247
if (!strcmp(key, "cpus"))
248
if ((h->n_cpus = atoi(value)) <= 0)
258
avahi_service_resolver_free(h->resolver);
261
/* Write modified hosts file */
262
write_hosts(h->daemon_data);
267
case AVAHI_RESOLVER_FAILURE:
269
rs_log_warning("Failed to resolve service '%s': %s\n", name,
270
avahi_strerror(avahi_client_errno(h->daemon_data->client)));
278
/* Called whenever a new service is found or removed */
279
static void browse_reply(
280
AvahiServiceBrowser *UNUSED(b),
281
AvahiIfIndex interface,
282
AvahiProtocol protocol,
283
AvahiBrowserEvent event,
287
AvahiLookupResultFlags UNUSED(flags),
290
struct daemon_data *d = userdata;
294
case AVAHI_BROWSER_NEW: {
297
h = malloc(sizeof(struct host));
300
rs_log_info("new service: %s\n", name);
302
if (!(h->resolver = avahi_service_resolver_new(d->client,
312
rs_log_warning("Failed to create service resolver for '%s': %s\n", name,
313
avahi_strerror(avahi_client_errno(d->client)));
319
/* Fill in missing data */
320
h->service = strdup(name);
322
h->domain = strdup(domain);
325
h->interface = interface;
326
h->protocol = protocol;
335
case AVAHI_BROWSER_REMOVE:
337
rs_log_info("Removed service: %s\n", name);
339
remove_service(d, interface, protocol, name, domain);
343
case AVAHI_BROWSER_FAILURE:
344
rs_log_crit("Service Browser failure '%s': %s\n", name,
345
avahi_strerror(avahi_client_errno(d->client)));
347
avahi_simple_poll_quit(d->simple_poll);
350
case AVAHI_BROWSER_CACHE_EXHAUSTED:
351
case AVAHI_BROWSER_ALL_FOR_NOW:
357
static void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) {
358
struct daemon_data *d = userdata;
362
case AVAHI_CLIENT_FAILURE:
363
rs_log_crit("Client failure: %s\n", avahi_strerror(avahi_client_errno(client)));
364
avahi_simple_poll_quit(d->simple_poll);
367
case AVAHI_CLIENT_S_COLLISION:
368
case AVAHI_CLIENT_S_REGISTERING:
369
case AVAHI_CLIENT_S_RUNNING:
370
case AVAHI_CLIENT_CONNECTING:
375
/* The main function of the background daemon */
376
static int daemon_proc(const char *host_file, const char *lock_file, int n_slots) {
379
struct daemon_data d;
382
char machine[64], version[64], stype[128];
384
rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0);
386
/* Prepare daemon data structure */
390
d.simple_poll = NULL;
393
clip_time = time(NULL);
395
rs_log_info("Zeroconf daemon running.\n");
397
/* Open daemon lock file and lock it */
398
if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) {
399
rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno));
403
if (generic_lock(lock_fd, 1, 1, 0) < 0) {
404
/* lock failed, there's probably already another daemon running */
409
if ((d.fd = open(host_file, O_RDWR|O_CREAT, 0666)) < 0) {
410
rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno));
414
/* Clear host file */
417
if (!(d.simple_poll = avahi_simple_poll_new())) {
418
rs_log_crit("Failed to create simple poll object.\n");
422
if (!(d.client = avahi_client_new(
423
avahi_simple_poll_get(d.simple_poll),
428
rs_log_crit("Failed to create Avahi client object: %s\n", avahi_strerror(error));
432
if (dcc_get_gcc_version(version, sizeof(version)) &&
433
dcc_get_gcc_machine(machine, sizeof(machine))) {
435
dcc_make_dnssd_subtype(stype, sizeof(stype), version, machine);
437
rs_log_warning("Warning, failed to get CC version and machine type.\n");
439
strncpy(stype, DCC_DNS_SERVICE_TYPE, sizeof(stype));
440
stype[sizeof(stype)-1] = 0;
443
rs_log_info("Browsing for '%s'.\n", stype);
445
if (!(d.browser = avahi_service_browser_new(
454
rs_log_crit("Failed to create service browser object: %s\n", avahi_strerror(avahi_client_errno(d.client)));
458
/* Check whether the host file has been used recently */
459
while (fd_last_used(d.fd, clip_time) <= MAX_IDLE_TIME) {
461
/* Iterate the main loop for 5s */
462
if (avahi_simple_poll_iterate(d.simple_poll, 5000) != 0) {
463
rs_log_crit("Event loop exited abnormaly.\n");
469
rs_log_info("Zeroconf daemon unused.\n");
477
generic_lock(lock_fd, 1, 0, 0);
485
struct host *h = d.hosts;
486
d.hosts = d.hosts->next;
491
avahi_client_free(d.client);
494
avahi_simple_poll_free(d.simple_poll);
496
rs_log_info("zeroconf daemon ended.\n");
501
/* Return path to the zeroconf directory in ~/.distcc */
502
static int get_zeroconf_dir(char **dir_ret) {
510
ret = dcc_get_subdir("zeroconf", dir_ret);
517
/* Get the host list from zeroconf */
518
int dcc_zeroconf_add_hosts(struct dcc_hostdef **ret_list, int *ret_nhosts, int n_slots, struct dcc_hostdef **ret_prev) {
519
char *host_file = NULL, *lock_file = NULL, *s = NULL;
520
int lock_fd = -1, host_fd = -1;
526
if (get_zeroconf_dir(&dir) != 0) {
527
rs_log_crit("failed to get zeroconf dir.\n");
531
lock_file = malloc(strlen(dir) + sizeof("/lock"));
533
sprintf(lock_file, "%s/lock", dir);
535
host_file = malloc(strlen(dir) + sizeof("/hosts"));
537
sprintf(host_file, "%s/hosts", dir);
540
if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) {
541
rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno));
545
/* Try to lock the lock file */
546
if (generic_lock(lock_fd, 1, 1, 0) >= 0) {
547
/* The lock succeeded => there's no daemon running yet! */
549
generic_lock(lock_fd, 1, 0, 0);
554
/* Shall we fork a new daemon? */
558
rs_log_info("Spawning zeroconf daemon.\n");
560
if ((pid = fork()) == -1) {
561
rs_log_crit("fork() failed: %s\n", strerror(errno));
563
} else if (pid == 0) {
567
/* Close file descriptors and replace them by /dev/null */
571
fd = open("/dev/null", O_RDWR);
583
rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0);
584
_exit(daemon_proc(host_file, lock_file, n_slots));
589
/* Wait some time for initial host gathering */
590
usleep(1000000); /* 1000 ms */
593
/* Open host list read-only */
594
if ((host_fd = open(host_file, O_RDONLY)) < 0) {
595
rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno));
600
if (generic_lock(host_fd, 0, 1, 1) < 0) {
601
rs_log_crit("lock failed: %s\n", strerror(errno));
606
if (fstat(host_fd, &st) < 0) {
607
rs_log_crit("stat() failed: %s\n", strerror(errno));
611
if (st.st_size >= MAX_FILE_SIZE) {
612
rs_log_crit("file too large.\n");
617
s = malloc((size_t) st.st_size+1);
620
if (dcc_readx(host_fd, s, (size_t) st.st_size) != 0) {
621
rs_log_crit("failed to read from file.\n");
626
/* Parse host data */
627
if (dcc_parse_hosts(s, host_file, ret_list, ret_nhosts, ret_prev) != 0) {
628
rs_log_crit("failed to parse host file.\n");
636
generic_lock(host_fd, 0, 0, 1);