Line 0
Link Here
|
|
|
1 |
/* -*- c-file-style: "java"; indent-tabs-mode: nil -*- */ |
2 |
|
3 |
#include "config.h" |
4 |
|
5 |
#include <assert.h> |
6 |
#include <stdio.h> |
7 |
#include <sys/select.h> |
8 |
#include <signal.h> |
9 |
#include <sys/file.h> |
10 |
#include <sys/time.h> |
11 |
#include <time.h> |
12 |
#include <sys/stat.h> |
13 |
|
14 |
#include "distcc.h" |
15 |
#include "hosts.h" |
16 |
#include "zeroconf.h" |
17 |
#include "trace.h" |
18 |
#include "exitcode.h" |
19 |
|
20 |
/* How long shall the background daemon be idle before i terminates itself? */ |
21 |
#define MAX_IDLE_TIME 20 |
22 |
|
23 |
/* Maxium size of host file to load */ |
24 |
#define MAX_FILE_SIZE (1024*100) |
25 |
|
26 |
/* Zeroconf service wrapper */ |
27 |
struct host { |
28 |
struct host *next; |
29 |
sw_ipv4_address address; |
30 |
sw_port port; |
31 |
sw_uint32 iface; |
32 |
char *service; |
33 |
char *domain; |
34 |
int n_cpus; |
35 |
}; |
36 |
|
37 |
/* General daemon data */ |
38 |
struct daemon_data { |
39 |
struct host *hosts; |
40 |
int fd; |
41 |
int n_slots; |
42 |
}; |
43 |
|
44 |
/* A generic, system independant lock routine, similar to sys_lock, |
45 |
* but more powerful: |
46 |
* rw: if non-zero: r/w lock instead of r/o lock |
47 |
* enable: lock or unlock |
48 |
* block: block when locking */ |
49 |
static int generic_lock(int fd, int rw, int enable, int block) { |
50 |
#if defined(F_SETLK) |
51 |
struct flock lockparam; |
52 |
|
53 |
lockparam.l_type = enable ? (rw ? F_WRLCK : F_RDLCK) : F_UNLCK; |
54 |
lockparam.l_whence = SEEK_SET; |
55 |
lockparam.l_start = 0; |
56 |
lockparam.l_len = 0; /* whole file */ |
57 |
|
58 |
return fcntl(fd, block ? F_SETLKW : F_SETLK, &lockparam); |
59 |
#elif defined(HAVE_FLOCK) |
60 |
return flock(fd, (enable ? (rw ? LOCK_EX : LOCK_SH) : LOCK_UN) | (block ? LOCK_NB : 0)); |
61 |
#elif defined(HAVE_LOCKF) |
62 |
return lockf(fd, (enable ? (block ? F_LOCK : F_TLOCK) : F_ULOCK)); |
63 |
#else |
64 |
# error "No supported lock method. Please port this code." |
65 |
#endif |
66 |
} |
67 |
|
68 |
/* Return the number of seconds, when the specified file was last |
69 |
* read. If the atime of that file is < clip_time, use clip_time |
70 |
* instead */ |
71 |
static time_t fd_last_used(int fd, time_t clip_time) { |
72 |
struct stat st; |
73 |
time_t now, ft; |
74 |
assert(fd >= 0); |
75 |
|
76 |
if (fstat(fd, &st) < 0) { |
77 |
rs_log_crit("fstat() failed: %s\n", strerror(errno)); |
78 |
return -1; |
79 |
} |
80 |
|
81 |
if ((now = time(NULL)) == (time_t) -1) { |
82 |
rs_log_crit("time() failed: %s\n", strerror(errno)); |
83 |
return -1; |
84 |
} |
85 |
|
86 |
ft = clip_time ? (st.st_atime < clip_time ? clip_time : st.st_atime) : st.st_atime; |
87 |
assert(ft <= now); |
88 |
|
89 |
return now - ft; |
90 |
} |
91 |
|
92 |
/* Write host data to host file */ |
93 |
static int write_hosts(struct daemon_data *d) { |
94 |
struct host *h; |
95 |
int r = 0; |
96 |
assert(d); |
97 |
|
98 |
rs_log_info("writing zeroconf data.\n"); |
99 |
|
100 |
if (generic_lock(d->fd, 1, 1, 1) < 0) { |
101 |
rs_log_crit("lock failed: %s\n", strerror(errno)); |
102 |
return -1; |
103 |
} |
104 |
|
105 |
if (lseek(d->fd, 0, SEEK_SET) < 0) { |
106 |
rs_log_crit("lseek() failed: %s\n", strerror(errno)); |
107 |
return -1; |
108 |
} |
109 |
|
110 |
if (ftruncate(d->fd, 0) < 0) { |
111 |
rs_log_crit("ftruncate() failed: %s\n", strerror(errno)); |
112 |
return -1; |
113 |
} |
114 |
|
115 |
for (h = d->hosts; h; h = h->next) { |
116 |
char t[256], a[16]; |
117 |
snprintf(t, sizeof(t), "%s:%u/%i\n", sw_ipv4_address_name(h->address, a, sizeof(a)), h->port, d->n_slots * h->n_cpus); |
118 |
|
119 |
if (dcc_writex(d->fd, t, strlen(t)) != 0) { |
120 |
rs_log_crit("write() failed: %s\n", strerror(errno)); |
121 |
goto finish; |
122 |
} |
123 |
} |
124 |
|
125 |
r = 0; |
126 |
|
127 |
finish: |
128 |
|
129 |
generic_lock(d->fd, 1, 0, 1); |
130 |
return r; |
131 |
|
132 |
}; |
133 |
|
134 |
/* Free host data */ |
135 |
static void free_host(struct host *h) { |
136 |
assert(h); |
137 |
free(h->service); |
138 |
free(h->domain); |
139 |
free(h); |
140 |
} |
141 |
|
142 |
/* Remove a service from the host list */ |
143 |
static void remove_service(struct daemon_data *d, sw_uint32 iface, const char *name, const char *domain) { |
144 |
struct host *h, *p = NULL; |
145 |
assert(d); |
146 |
|
147 |
for (h = d->hosts; h; h = h->next) { |
148 |
if (h->iface == iface && !strcmp(h->service, name) && !strcmp(h->domain, domain)) { |
149 |
|
150 |
if (p) |
151 |
p->next = h->next; |
152 |
else |
153 |
d->hosts = h->next; |
154 |
|
155 |
free_host(h); |
156 |
|
157 |
break; |
158 |
} else |
159 |
p = h; |
160 |
} |
161 |
} |
162 |
|
163 |
/* Called when a resolve call completes */ |
164 |
static sw_result resolve_reply( |
165 |
sw_discovery discovery, |
166 |
sw_discovery_oid oid, |
167 |
sw_uint32 interface_index, |
168 |
sw_const_string name, |
169 |
sw_const_string type, |
170 |
sw_const_string domain, |
171 |
sw_ipv4_address address, |
172 |
sw_port port, |
173 |
sw_octets text_record, |
174 |
sw_ulong text_record_len, |
175 |
sw_opaque extra) { |
176 |
|
177 |
struct host *h; |
178 |
struct daemon_data *d = extra; |
179 |
int n_cpus = 1; |
180 |
sw_text_record_iterator it; |
181 |
|
182 |
/* Look for the number of CPUs in TXT RRs */ |
183 |
if (sw_text_record_iterator_init(&it, text_record, text_record_len) == SW_OKAY) { |
184 |
sw_char key[255]; |
185 |
sw_octet val[256]; |
186 |
sw_ulong val_len = sizeof(val)-1; |
187 |
|
188 |
memset(val, 0, sizeof(val)); |
189 |
|
190 |
while (sw_text_record_iterator_next(it, key, val, &val_len) == SW_OKAY) { |
191 |
if (!strcmp(key, "cpus")) { |
192 |
if ((n_cpus = atoi((char*) val)) <= 0) |
193 |
n_cpus = 1; |
194 |
} |
195 |
} |
196 |
|
197 |
sw_text_record_iterator_fina(it); |
198 |
} |
199 |
|
200 |
/* Add a new host */ |
201 |
h = malloc(sizeof(struct host)); |
202 |
assert(h); |
203 |
h->service = strdup(name); |
204 |
assert(h->service); |
205 |
h->domain = strdup(domain); |
206 |
assert(h->domain); |
207 |
h->address = address; |
208 |
h->port = port; |
209 |
h->iface = interface_index; |
210 |
h->next = d->hosts; |
211 |
h->n_cpus = n_cpus; |
212 |
d->hosts = h; |
213 |
|
214 |
/* Write modified hosts file */ |
215 |
write_hosts(d); |
216 |
|
217 |
/* Let's cancel this resolve request, we only need a single adress for this service */ |
218 |
sw_discovery_cancel(discovery, oid); |
219 |
|
220 |
return SW_OKAY; |
221 |
} |
222 |
|
223 |
/* Called whenever a new service is found or removed */ |
224 |
static sw_result browse_reply( |
225 |
sw_discovery discovery, |
226 |
sw_discovery_oid oid, |
227 |
sw_discovery_browse_status status, |
228 |
sw_uint32 interface_index, |
229 |
sw_const_string name, |
230 |
sw_const_string type, |
231 |
sw_const_string domain, |
232 |
sw_opaque extra) { |
233 |
|
234 |
struct daemon_data *d = extra; |
235 |
assert(d); |
236 |
|
237 |
switch (status) { |
238 |
case SW_DISCOVERY_BROWSE_ADD_SERVICE: { |
239 |
sw_discovery_oid noid; |
240 |
|
241 |
rs_log_info("new service: %s\n", name); |
242 |
|
243 |
sw_discovery_resolve(discovery, 0, name, type, domain, resolve_reply, extra, &noid); |
244 |
break; |
245 |
} |
246 |
case SW_DISCOVERY_BROWSE_REMOVE_SERVICE: { |
247 |
|
248 |
rs_log_info("removed service: %s\n", name); |
249 |
|
250 |
remove_service(d, interface_index, name, domain); |
251 |
write_hosts(d); |
252 |
break; |
253 |
} |
254 |
default: |
255 |
; |
256 |
} |
257 |
|
258 |
return SW_OKAY; |
259 |
} |
260 |
|
261 |
/* The main function of the background daemon */ |
262 |
static void daemon_proc(const char *host_file, const char *lock_file, int n_slots) { |
263 |
sw_discovery_oid oid; |
264 |
sw_discovery discovery; |
265 |
int ret = 1; |
266 |
int lock_fd = -1; |
267 |
struct daemon_data d; |
268 |
sw_salt salt; |
269 |
time_t clip_time; |
270 |
|
271 |
/* Prepare daemon data structure */ |
272 |
d.fd = -1; |
273 |
d.hosts = NULL; |
274 |
d.n_slots = n_slots; |
275 |
clip_time = time(NULL); |
276 |
|
277 |
rs_log_info("zeroconf daemon running.\n"); |
278 |
|
279 |
/* Open daemon lock file and lock it */ |
280 |
if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) { |
281 |
rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno)); |
282 |
goto finish; |
283 |
} |
284 |
|
285 |
if (generic_lock(lock_fd, 1, 1, 0) < 0) { |
286 |
/* lock failed, there's probably already another daemon running */ |
287 |
goto finish; |
288 |
} |
289 |
|
290 |
/* Open host file */ |
291 |
if ((d.fd = open(host_file, O_RDWR|O_CREAT, 0666)) < 0) { |
292 |
rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno)); |
293 |
goto finish; |
294 |
} |
295 |
|
296 |
/* Clear host file */ |
297 |
write_hosts(&d); |
298 |
|
299 |
if (sw_discovery_init(&discovery) != SW_OKAY) { |
300 |
rs_log_crit("sw_discovery_init() failed.\n"); |
301 |
goto finish; |
302 |
} |
303 |
|
304 |
/* Start discovery */ |
305 |
if (sw_discovery_browse(discovery, 0, "_distcc._tcp", NULL, browse_reply, &d, &oid) != SW_OKAY) { |
306 |
rs_log_crit("sw_discovery_browse() failed.\n"); |
307 |
goto finish; |
308 |
} |
309 |
|
310 |
/* Get "salt" object for the main loop */ |
311 |
if (sw_discovery_salt(discovery, &salt) != SW_OKAY) { |
312 |
rs_log_crit("sw_discovery_salt() failed.\n"); |
313 |
goto finish; |
314 |
} |
315 |
|
316 |
/* Check whether the host file has been used recently */ |
317 |
while (fd_last_used(d.fd, clip_time) <= MAX_IDLE_TIME) { |
318 |
sw_ulong msecs = 500; |
319 |
|
320 |
/* Iterate the main loop for 500ms */ |
321 |
if (sw_salt_step(salt, &msecs) != SW_OKAY) { |
322 |
rs_log_crit("sw_salt_step() failed.\n"); |
323 |
goto finish; |
324 |
} |
325 |
} |
326 |
|
327 |
/* Wer are idle */ |
328 |
rs_log_info("zeroconf daemon unused.\n"); |
329 |
|
330 |
ret = 0; |
331 |
|
332 |
finish: |
333 |
|
334 |
/* Cleanup */ |
335 |
if (lock_fd >= 0) { |
336 |
generic_lock(lock_fd, 1, 0, 0); |
337 |
close(lock_fd); |
338 |
} |
339 |
|
340 |
if (d.fd >= 0) |
341 |
close(d.fd); |
342 |
|
343 |
while (d.hosts) { |
344 |
struct host *h = d.hosts; |
345 |
d.hosts = d.hosts->next; |
346 |
free_host(h); |
347 |
} |
348 |
|
349 |
rs_log_info("zeroconf daemon ended.\n"); |
350 |
|
351 |
exit(ret); |
352 |
} |
353 |
|
354 |
/* Return path to the zeroconf directory in ~/.distcc */ |
355 |
static int get_zeroconf_dir(char **dir_ret) { |
356 |
static char *cached; |
357 |
int ret; |
358 |
|
359 |
if (cached) { |
360 |
*dir_ret = cached; |
361 |
return 0; |
362 |
} else { |
363 |
ret = dcc_get_subdir("zeroconf", dir_ret); |
364 |
if (ret == 0) |
365 |
cached = *dir_ret; |
366 |
return ret; |
367 |
} |
368 |
} |
369 |
|
370 |
/* Get the host list from zeroconf */ |
371 |
int dcc_zeroconf_add_hosts(struct dcc_hostdef **ret_list, int *ret_nhosts, int n_slots, struct dcc_hostdef **ret_prev) { |
372 |
char host_file[PATH_MAX], lock_file[PATH_MAX], *s = NULL; |
373 |
int lock_fd = -1, host_fd = -1; |
374 |
int fork_daemon = 0; |
375 |
int r = -1; |
376 |
char *dir; |
377 |
struct stat st; |
378 |
|
379 |
if (get_zeroconf_dir(&dir) != 0) { |
380 |
rs_log_crit("failed to get zeroconf dir.\n"); |
381 |
goto finish; |
382 |
} |
383 |
|
384 |
snprintf(lock_file, sizeof(lock_file), "%s/lock", dir); |
385 |
snprintf(host_file, sizeof(host_file), "%s/hosts", dir); |
386 |
|
387 |
/* Open lock file */ |
388 |
if ((lock_fd = open(lock_file, O_RDWR|O_CREAT, 0666)) < 0) { |
389 |
rs_log_crit("open('%s') failed: %s\n", lock_file, strerror(errno)); |
390 |
goto finish; |
391 |
} |
392 |
|
393 |
/* Try to lock the lock file */ |
394 |
if (generic_lock(lock_fd, 1, 1, 0) >= 0) { |
395 |
/* The lock succeeded => there's no daemon running yet! */ |
396 |
fork_daemon = 1; |
397 |
generic_lock(lock_fd, 1, 0, 0); |
398 |
} |
399 |
|
400 |
close(lock_fd); |
401 |
|
402 |
/* Shall we fork a new daemon? */ |
403 |
if (fork_daemon) { |
404 |
pid_t pid; |
405 |
|
406 |
rs_log_info("Spawning zeroconf daemon.\n"); |
407 |
|
408 |
if ((pid = fork()) == -1) { |
409 |
rs_log_crit("fork() failed: %s\n", strerror(errno)); |
410 |
goto finish; |
411 |
} else if (pid == 0) { |
412 |
int fd; |
413 |
/* Child */ |
414 |
|
415 |
/* Close file descriptors and replace them by /dev/null */ |
416 |
close(0); |
417 |
close(1); |
418 |
close(2); |
419 |
fd = open("/dev/null", O_RDWR); |
420 |
assert(fd == 0); |
421 |
fd = dup(0); |
422 |
assert(fd == 1); |
423 |
fd = dup(0); |
424 |
assert(fd == 2); |
425 |
|
426 |
#ifdef HAVE_SETSID |
427 |
setsid(); |
428 |
#endif |
429 |
|
430 |
chdir("/"); |
431 |
rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0); |
432 |
daemon_proc(host_file, lock_file, n_slots); |
433 |
} |
434 |
|
435 |
/* Parent */ |
436 |
|
437 |
/* Wait some time for initial host gathering */ |
438 |
usleep(1000000); /* 100 ms */ |
439 |
|
440 |
} |
441 |
|
442 |
/* Open host list read-only */ |
443 |
if ((host_fd = open(host_file, O_RDONLY)) < 0) { |
444 |
rs_log_crit("open('%s') failed: %s\n", host_file, strerror(errno)); |
445 |
goto finish; |
446 |
} |
447 |
|
448 |
/* A read lock */ |
449 |
if (generic_lock(host_fd, 0, 1, 1) < 0) { |
450 |
rs_log_crit("lock failed: %s\n", strerror(errno)); |
451 |
goto finish; |
452 |
} |
453 |
|
454 |
/* Get file size */ |
455 |
if (fstat(host_fd, &st) < 0) { |
456 |
rs_log_crit("stat() failed: %s\n", strerror(errno)); |
457 |
goto finish; |
458 |
} |
459 |
|
460 |
if (st.st_size >= MAX_FILE_SIZE) { |
461 |
rs_log_crit("file too large.\n"); |
462 |
goto finish; |
463 |
} |
464 |
|
465 |
/* read file data */ |
466 |
s = malloc((size_t) st.st_size+1); |
467 |
assert(s); |
468 |
|
469 |
if (dcc_readx(host_fd, s, (size_t) st.st_size) != 0) { |
470 |
rs_log_crit("failed to read from file.\n"); |
471 |
goto finish; |
472 |
} |
473 |
s[st.st_size] = 0; |
474 |
|
475 |
/* Parse host data */ |
476 |
if (dcc_parse_hosts(s, host_file, ret_list, ret_nhosts, ret_prev) != 0) { |
477 |
rs_log_crit("failed to parse host file.\n"); |
478 |
goto finish; |
479 |
} |
480 |
|
481 |
r = 0; |
482 |
|
483 |
finish: |
484 |
if (host_fd >= 0) { |
485 |
generic_lock(host_fd, 0, 0, 1); |
486 |
close(host_fd); |
487 |
} |
488 |
|
489 |
free(s); |
490 |
|
491 |
return r; |
492 |
} |
493 |
|