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