1 /* -*- c-file-style: "java"; indent-tabs-mode: nil; tab-width: 4; fill-column: 78 -*-
2 *
3 * Copyright (C) 2007 Lennart Poettering
4 *
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.
9 *
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.
14 *
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,
18 * USA.
19 */
20
21 #include <config.h>
22
23 #include <assert.h>
24 #include <stdio.h>
25 #include <sys/select.h>
26 #include <signal.h>
27 #include <sys/file.h>
28 #include <fcntl.h>
29 #include <sys/time.h>
30 #include <time.h>
31 #include <sys/stat.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <unistd.h>
35 #include <stdlib.h>
36 #include <limits.h>
37 #include <fcntl.h>
38
39 #include <avahi-common/domain.h>
40 #include <avahi-common/error.h>
41 #include <avahi-common/malloc.h>
42 #include <avahi-common/address.h>
43 #include <avahi-common/simple-watch.h>
44 #include <avahi-client/lookup.h>
45
46 #include "distcc.h"
47 #include "hosts.h"
48 #include "zeroconf.h"
49 #include "trace.h"
50 #include "exitcode.h"
51
52 /* How long shall the background daemon be idle before i terminates itself? */
53 #define MAX_IDLE_TIME 20
54
55 /* Maximum size of host file to load */
56 #define MAX_FILE_SIZE (1024*100)
57
58 /* General daemon data */
59 struct daemon_data {
60 struct host *hosts;
61 int fd;
62 int n_slots;
63
64 AvahiClient *client;
65 AvahiServiceBrowser *browser;
66 AvahiSimplePoll *simple_poll;
67 };
68
69 /* Zeroconf service wrapper */
70 struct host {
71 struct daemon_data *daemon_data;
72 struct host *next;
73
74 AvahiIfIndex interface;
75 AvahiProtocol protocol;
76 char *service;
77 char *domain;
78
79 AvahiAddress address;
80 uint16_t port;
81 int n_cpus;
82 int n_jobs;
83
84 AvahiServiceResolver *resolver;
85 };
86
87 /* A generic, system independent lock routine, similar to sys_lock,
88 * but more powerful:
89 * rw: if non-zero: r/w lock instead of r/o lock
90 * enable: lock or unlock
91 * block: block when locking */
generic_lock(int fd,int rw,int enable,int block)92 static int generic_lock(int fd, int rw, int enable, int block) {
93 #if defined(F_SETLK)
94 struct flock lockparam;
95
96 lockparam.l_type = enable ? (rw ? F_WRLCK : F_RDLCK) : F_UNLCK;
97 lockparam.l_whence = SEEK_SET;
98 lockparam.l_start = 0;
99 lockparam.l_len = 0; /* whole file */
100
101 return fcntl(fd, block ? F_SETLKW : F_SETLK, &lockparam);
102 #elif defined(HAVE_FLOCK)
103 return flock(fd, (enable ? (rw ? LOCK_EX : LOCK_SH) : LOCK_UN) | (block ? LOCK_NB : 0));
104 #elif defined(HAVE_LOCKF)
105 return lockf(fd, (enable ? (block ? F_LOCK : F_TLOCK) : F_ULOCK));
106 #else
107 # error "No supported lock method. Please port this code."
108 #endif
109 }
110
111 /* Return the number of seconds, when the specified file was last
112 * read. If the atime of that file is < clip_time, use clip_time
113 * instead */
fd_last_used(int fd,time_t clip_time)114 static time_t fd_last_used(int fd, time_t clip_time) {
115 struct stat st;
116 time_t now, ft;
117 assert(fd >= 0);
118
119 if (fstat(fd, &st) < 0) {
120 rs_log_crit("fstat() failed: %s\n", strerror(errno));
121 return -1;
122 }
123
124 if ((now = time(NULL)) == (time_t) -1) {
125 rs_log_crit("time() failed: %s\n", strerror(errno));
126 return -1;
127 }
128
129 ft = clip_time ? (st.st_atime < clip_time ? clip_time : st.st_atime) : st.st_atime;
130 assert(ft <= now);
131
132 return now - ft;
133 }
134
135 static void remove_duplicate_services(struct daemon_data *d);
136
137 /* Write host data to host file */
write_hosts(struct daemon_data * d)138 static int write_hosts(struct daemon_data *d) {
139 struct host *h;
140 int r = 0;
141 assert(d);
142
143 rs_log_info("writing zeroconf data.\n");
144
145 if (generic_lock(d->fd, 1, 1, 1) < 0) {
146 rs_log_crit("lock failed: %s\n", strerror(errno));
147 return -1;
148 }
149
150 if (lseek(d->fd, 0, SEEK_SET) < 0) {
151 rs_log_crit("lseek() failed: %s\n", strerror(errno));
152 return -1;
153 }
154
155 if (ftruncate(d->fd, 0) < 0) {
156 rs_log_crit("ftruncate() failed: %s\n", strerror(errno));
157 return -1;
158 }
159
160 remove_duplicate_services(d);
161
162 for (h = d->hosts; h; h = h->next) {
163 char t[256], a[AVAHI_ADDRESS_STR_MAX];
164
165 if (h->resolver)
166 /* Not yet fully resolved */
167 continue;
168 if (h->address.proto == AVAHI_PROTO_INET6)
169 snprintf(t, sizeof(t), "[%s]:%u/%i\n", avahi_address_snprint(a, sizeof(a), &h->address), h->port, h->n_jobs);
170 else
171 snprintf(t, sizeof(t), "%s:%u/%i\n", avahi_address_snprint(a, sizeof(a), &h->address), h->port, h->n_jobs);
172
173 if (dcc_writex(d->fd, t, strlen(t)) != 0) {
174 rs_log_crit("write() failed: %s\n", strerror(errno));
175 goto finish;
176 }
177 }
178
179 r = 0;
180
181 finish:
182
183 generic_lock(d->fd, 1, 0, 1);
184 return r;
185
186 };
187
188 /* Free host data */
free_host(struct host * h)189 static void free_host(struct host *h) {
190 assert(h);
191
192 if (h->resolver)
193 avahi_service_resolver_free(h->resolver);
194
195 free(h->service);
196 free(h->domain);
197 free(h);
198 }
199
200 /* Remove hosts with duplicate service names from the host list.
201 * Hosts with multiple IP addresses show up more than once, but
202 * should all have the same service name: "distcc@hostname" */
remove_duplicate_services(struct daemon_data * d)203 static void remove_duplicate_services(struct daemon_data *d) {
204 struct host *host1, *host2, *prev;
205 assert(d);
206 for (host1 = d->hosts; host1; host1 = host1->next) {
207 assert(host1->service);
208 for (host2 = host1->next, prev = host1; host2; host2 = prev->next) {
209 assert(host2->service);
210 if (!strcmp(host1->service, host2->service)) {
211 prev->next = host2->next;
212 free_host(host2);
213 } else {
214 prev = host2;
215 }
216 }
217 }
218 }
219
220 /* Remove a service from the host list */
remove_service(struct daemon_data * d,AvahiIfIndex interface,AvahiProtocol protocol,const char * name,const char * domain)221 static void remove_service(struct daemon_data *d, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *domain) {
222 struct host *h, *p = NULL;
223 assert(d);
224
225 for (h = d->hosts; h; h = h->next) {
226 if (h->interface == interface &&
227 h->protocol == protocol &&
228 !strcmp(h->service, name) &&
229 avahi_domain_equal(h->domain, domain)) {
230
231 if (p)
232 p->next = h->next;
233 else
234 d->hosts = h->next;
235
236 free_host(h);
237
238 break;
239 } else
240 p = h;
241 }
242 }
243
244 /* Called when a resolve call completes */
resolve_reply(AvahiServiceResolver * UNUSED (r),AvahiIfIndex UNUSED (interface),AvahiProtocol UNUSED (protocol),AvahiResolverEvent event,const char * name,const char * UNUSED (type),const char * UNUSED (domain),const char * UNUSED (host_name),const AvahiAddress * a,uint16_t port,AvahiStringList * txt,AvahiLookupResultFlags UNUSED (flags),void * userdata)245 static void resolve_reply(
246 AvahiServiceResolver *UNUSED(r),
247 AvahiIfIndex UNUSED(interface),
248 AvahiProtocol UNUSED(protocol),
249 AvahiResolverEvent event,
250 const char *name,
251 const char *UNUSED(type),
252 const char *UNUSED(domain),
253 const char *UNUSED(host_name),
254 const AvahiAddress *a,
255 uint16_t port,
256 AvahiStringList *txt,
257 AvahiLookupResultFlags UNUSED(flags),
258 void *userdata) {
259
260 struct host *h = userdata;
261
262 switch (event) {
263
264 case AVAHI_RESOLVER_FOUND: {
265 AvahiStringList *i;
266
267 /* Look for the number of CPUs in TXT RRs */
268 for (i = txt; i; i = i->next) {
269 char *key, *value;
270
271 if (avahi_string_list_get_pair(i, &key, &value, NULL) < 0)
272 continue;
273
274 if (!strcmp(key, "cpus"))
275 if ((h->n_cpus = atoi(value)) <= 0)
276 h->n_cpus = 1;
277
278 avahi_free(key);
279 avahi_free(value);
280 }
281
282 /* Look for the number of jobs in TXT RRs, and if not found, then set n_jobs = n_cpus + 2 */
283 for (i = txt; i; i = i->next) {
284 char *key, *value;
285
286 if (avahi_string_list_get_pair(i, &key, &value, NULL) < 0)
287 continue;
288
289 if (!strcmp(key, "jobs"))
290 if ((h->n_jobs = atoi(value)) <= 0)
291 h->n_jobs = h->n_cpus + 2;
292
293 avahi_free(key);
294 avahi_free(value);
295 }
296
297 h->address = *a;
298 h->port = port;
299
300 avahi_service_resolver_free(h->resolver);
301 h->resolver = NULL;
302
303 /* Write modified hosts file */
304 write_hosts(h->daemon_data);
305
306 break;
307 }
308
309 case AVAHI_RESOLVER_FAILURE:
310
311 rs_log_warning("Failed to resolve service '%s': %s\n", name,
312 avahi_strerror(avahi_client_errno(h->daemon_data->client)));
313
314 free_host(h);
315 break;
316 }
317
318 }
319
320 /* Called whenever a new service is found or removed */
browse_reply(AvahiServiceBrowser * UNUSED (b),AvahiIfIndex interface,AvahiProtocol protocol,AvahiBrowserEvent event,const char * name,const char * type,const char * domain,AvahiLookupResultFlags UNUSED (flags),void * userdata)321 static void browse_reply(
322 AvahiServiceBrowser *UNUSED(b),
323 AvahiIfIndex interface,
324 AvahiProtocol protocol,
325 AvahiBrowserEvent event,
326 const char *name,
327 const char *type,
328 const char *domain,
329 AvahiLookupResultFlags UNUSED(flags),
330 void *userdata) {
331
332 struct daemon_data *d = userdata;
333 assert(d);
334
335 switch (event) {
336 case AVAHI_BROWSER_NEW: {
337 struct host *h;
338
339 h = malloc(sizeof(struct host));
340 assert(h);
341
342 rs_log_info("new service: %s\n", name);
343
344 if (!(h->resolver = avahi_service_resolver_new(d->client,
345 interface,
346 protocol,
347 name,
348 type,
349 domain,
350 AVAHI_PROTO_UNSPEC,
351 0,
352 resolve_reply,
353 h))) {
354 rs_log_warning("Failed to create service resolver for '%s': %s\n", name,
355 avahi_strerror(avahi_client_errno(d->client)));
356
357 free(h);
358
359 } else {
360
361 /* Fill in missing data */
362 h->service = strdup(name);
363 assert(h->service);
364 h->domain = strdup(domain);
365 assert(h->domain);
366 h->daemon_data = d;
367 h->interface = interface;
368 h->protocol = protocol;
369 h->next = d->hosts;
370 h->n_cpus = 1;
371 h->n_jobs = 4;
372 d->hosts = h;
373 }
374
375 break;
376 }
377
378 case AVAHI_BROWSER_REMOVE:
379
380 rs_log_info("Removed service: %s\n", name);
381
382 remove_service(d, interface, protocol, name, domain);
383 write_hosts(d);
384 break;
385
386 case AVAHI_BROWSER_FAILURE:
387 rs_log_crit("Service Browser failure '%s': %s\n", name,
388 avahi_strerror(avahi_client_errno(d->client)));
389
390 avahi_simple_poll_quit(d->simple_poll);
391 break;
392
393 case AVAHI_BROWSER_CACHE_EXHAUSTED:
394 case AVAHI_BROWSER_ALL_FOR_NOW:
395 ;
396
397 }
398 }
399
client_callback(AvahiClient * client,AvahiClientState state,void * userdata)400 static void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) {
401 struct daemon_data *d = userdata;
402
403 switch (state) {
404
405 case AVAHI_CLIENT_FAILURE:
406 rs_log_crit("Client failure: %s\n", avahi_strerror(avahi_client_errno(client)));
407 avahi_simple_poll_quit(d->simple_poll);
408 break;
409
410 case AVAHI_CLIENT_S_COLLISION:
411 case AVAHI_CLIENT_S_REGISTERING:
412 case AVAHI_CLIENT_S_RUNNING:
413 case AVAHI_CLIENT_CONNECTING:
414 ;
415 }
416 }
417
418 /* The main function of the background daemon */
daemon_proc(const char * host_file,const char * lock_file,int n_slots)419 static int daemon_proc(const char *host_file, const char *lock_file, int n_slots) {
420 int ret = 1;
421 int lock_fd = -1;
422 struct daemon_data d;
423 time_t clip_time;
424 int error;
425 char machine[64], version[64], stype[128];
426
427 rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0);
428
429 /* Prepare daemon data structure */
430 d.fd = -1;
431 d.hosts = NULL;
432 d.n_slots = n_slots;
433 d.simple_poll = NULL;
434 d.browser = NULL;
435 d.client = NULL;
436 clip_time = time(NULL);
437
438 rs_log_info("Zeroconf daemon running.\n");
439
440 /* Open daemon lock file and lock it */
441 if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) {
442 rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno));
443 goto finish;
444 }
445
446 if (generic_lock(lock_fd, 1, 1, 0) < 0) {
447 /* lock failed, there's probably already another daemon running */
448 goto finish;
449 }
450
451 /* Open host file */
452 if ((d.fd = open(host_file, O_RDWR|O_CREAT, 0666)) < 0) {
453 rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno));
454 goto finish;
455 }
456
457 /* Clear host file */
458 write_hosts(&d);
459
460 if (!(d.simple_poll = avahi_simple_poll_new())) {
461 rs_log_crit("Failed to create simple poll object.\n");
462 goto finish;
463 }
464
465 if (!(d.client = avahi_client_new(
466 avahi_simple_poll_get(d.simple_poll),
467 0,
468 client_callback,
469 &d,
470 &error))) {
471 rs_log_crit("Failed to create Avahi client object: %s\n", avahi_strerror(error));
472 goto finish;
473 }
474
475 if (dcc_get_gcc_version(version, sizeof(version)) &&
476 dcc_get_gcc_machine(machine, sizeof(machine))) {
477
478 dcc_make_dnssd_subtype(stype, sizeof(stype), version, machine);
479 } else {
480 rs_log_warning("Warning, failed to get CC version and machine type.\n");
481
482 strncpy(stype, DCC_DNS_SERVICE_TYPE, sizeof(stype));
483 stype[sizeof(stype)-1] = 0;
484 }
485
486 rs_log_info("Browsing for '%s'.\n", stype);
487
488 if (!(d.browser = avahi_service_browser_new(
489 d.client,
490 AVAHI_IF_UNSPEC,
491 AVAHI_PROTO_UNSPEC,
492 stype,
493 NULL,
494 0,
495 browse_reply,
496 &d))) {
497 rs_log_crit("Failed to create service browser object: %s\n", avahi_strerror(avahi_client_errno(d.client)));
498 goto finish;
499 }
500
501 /* Check whether the host file has been used recently */
502 while (fd_last_used(d.fd, clip_time) <= MAX_IDLE_TIME) {
503
504 /* Iterate the main loop for 5s */
505 if (avahi_simple_poll_iterate(d.simple_poll, 5000) != 0) {
506 rs_log_crit("Event loop exited abnormally.\n");
507 goto finish;
508 }
509 }
510
511 /* Wer are idle */
512 rs_log_info("Zeroconf daemon unused.\n");
513
514 ret = 0;
515
516 finish:
517
518 /* Cleanup */
519 if (lock_fd >= 0) {
520 generic_lock(lock_fd, 1, 0, 0);
521 close(lock_fd);
522 }
523
524 if (d.fd >= 0)
525 close(d.fd);
526
527 while (d.hosts) {
528 struct host *h = d.hosts;
529 d.hosts = d.hosts->next;
530 free_host(h);
531 }
532
533 if (d.client)
534 avahi_client_free(d.client);
535
536 if (d.simple_poll)
537 avahi_simple_poll_free(d.simple_poll);
538
539 rs_log_info("zeroconf daemon ended.\n");
540
541 return ret;
542 }
543
544 /* Return path to the zeroconf directory in ~/.distcc */
get_zeroconf_dir(char ** dir_ret)545 static int get_zeroconf_dir(char **dir_ret) {
546 static char *cached;
547 int ret;
548
549 if (cached) {
550 *dir_ret = cached;
551 return 0;
552 } else {
553 ret = dcc_get_subdir("zeroconf", dir_ret);
554 if (ret == 0)
555 cached = *dir_ret;
556 return ret;
557 }
558 }
559
560 /* Get the host list from zeroconf */
dcc_zeroconf_add_hosts(struct dcc_hostdef ** ret_list,int * ret_nhosts,int n_slots,struct dcc_hostdef ** ret_prev)561 int dcc_zeroconf_add_hosts(struct dcc_hostdef **ret_list, int *ret_nhosts, int n_slots, struct dcc_hostdef **ret_prev) {
562 char *host_file = NULL, *lock_file = NULL, *s = NULL;
563 int lock_fd = -1, host_fd = -1;
564 int fork_daemon = 0;
565 int r = -1;
566 char *dir;
567 struct stat st;
568
569 if (get_zeroconf_dir(&dir) != 0) {
570 rs_log_crit("failed to get zeroconf dir.\n");
571 goto finish;
572 }
573
574 lock_file = malloc(strlen(dir) + sizeof("/lock"));
575 assert(lock_file);
576 sprintf(lock_file, "%s/lock", dir);
577
578 host_file = malloc(strlen(dir) + sizeof("/hosts"));
579 assert(host_file);
580 sprintf(host_file, "%s/hosts", dir);
581
582 /* Open lock file */
583 if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) {
584 rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno));
585 goto finish;
586 }
587
588 /* Try to lock the lock file */
589 if (generic_lock(lock_fd, 1, 1, 0) >= 0) {
590 /* The lock succeeded => there's no daemon running yet! */
591 fork_daemon = 1;
592 generic_lock(lock_fd, 1, 0, 0);
593 }
594
595 close(lock_fd);
596
597 /* Shall we fork a new daemon? */
598 if (fork_daemon) {
599 pid_t pid;
600
601 rs_log_info("Spawning zeroconf daemon.\n");
602
603 if ((pid = fork()) == -1) {
604 rs_log_crit("fork() failed: %s\n", strerror(errno));
605 goto finish;
606 } else if (pid == 0) {
607 int max_fd, fd;
608 /* Child */
609
610 /* Close file descriptors and replace them by /dev/null */
611 max_fd = (int)sysconf(_SC_OPEN_MAX);
612 if(max_fd == -1) {
613 max_fd = 1024;
614 }
615 for(fd = 0; fd < max_fd; fd++) {
616 close(fd);
617 }
618 fd = open("/dev/null", O_RDWR);
619 assert(fd == 0);
620 fd = dup(0);
621 assert(fd == 1);
622 fd = dup(0);
623 assert(fd == 2);
624
625 #ifdef HAVE_SETSID
626 setsid();
627 #endif
628
629 int ret = chdir("/");
630 rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0);
631 if (ret != 0) {
632 rs_log_warning("chdir to '/' failed: %s", strerror(errno));
633 }
634 _exit(daemon_proc(host_file, lock_file, n_slots));
635 }
636
637 /* Parent */
638
639 /* Wait some time for initial host gathering */
640 usleep(1000000); /* 1000 ms */
641 }
642
643 /* Open host list read-only */
644 if ((host_fd = open(host_file, O_RDONLY)) < 0) {
645 rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno));
646 goto finish;
647 }
648
649 /* A read lock */
650 if (generic_lock(host_fd, 0, 1, 1) < 0) {
651 rs_log_crit("lock failed: %s\n", strerror(errno));
652 goto finish;
653 }
654
655 /* Get file size */
656 if (fstat(host_fd, &st) < 0) {
657 rs_log_crit("stat() failed: %s\n", strerror(errno));
658 goto finish;
659 }
660
661 if (st.st_size >= MAX_FILE_SIZE) {
662 rs_log_crit("file too large.\n");
663 goto finish;
664 }
665
666 /* read file data */
667 s = malloc((size_t) st.st_size+1);
668 assert(s);
669
670 if (dcc_readx(host_fd, s, (size_t) st.st_size) != 0) {
671 rs_log_crit("failed to read from file.\n");
672 goto finish;
673 }
674 s[st.st_size] = 0;
675
676 /* Parse host data */
677 if (dcc_parse_hosts(s, host_file, ret_list, ret_nhosts, ret_prev) != 0) {
678 rs_log_crit("failed to parse host file.\n");
679 goto finish;
680 }
681
682 r = 0;
683
684 finish:
685 if (host_fd >= 0) {
686 generic_lock(host_fd, 0, 0, 1);
687 close(host_fd);
688 }
689
690 free(lock_file);
691 free(host_file);
692 free(s);
693
694 return r;
695 }
696