]> git.meshlink.io Git - catta/blob - avahi-utils/avahi-browse.c
0bf58efae233ff5bc91b754096dc7e9503e8bbb8
[catta] / avahi-utils / avahi-browse.c
1 /* $Id$ */
2
3 /***
4   This file is part of avahi.
5  
6   avahi is free software; you can redistribute it and/or modify it
7   under the terms of the GNU Lesser General Public License as
8   published by the Free Software Foundation; either version 2.1 of the
9   License, or (at your option) any later version.
10  
11   avahi is distributed in the hope that it will be useful, but WITHOUT
12   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14   Public License for more details.
15  
16   You should have received a copy of the GNU Lesser General Public
17   License along with avahi; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19   USA.
20 ***/
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <getopt.h>
29 #include <assert.h>
30 #include <string.h>
31 #include <net/if.h>
32 #include <locale.h>
33
34 #include <avahi-common/simple-watch.h>
35 #include <avahi-common/error.h>
36 #include <avahi-common/malloc.h>
37 #include <avahi-common/domain.h>
38 #include <avahi-common/llist.h>
39 #include <avahi-client/client.h>
40 #include <avahi-client/lookup.h>
41
42 #include "sigint.h"
43
44 #ifdef HAVE_GDBM
45 #include "stdb.h"
46 #endif
47
48 typedef enum {
49     COMMAND_HELP,
50     COMMAND_VERSION,
51     COMMAND_RUN
52 } Command;
53
54 typedef struct Config {
55     int verbose;
56     int terminate_on_all_for_now;
57     int terminate_on_cache_exhausted;
58     char *domain;
59     char *stype;
60     int ignore_local;
61     int show_all;
62     Command command;
63     int resolve;
64 #ifdef HAVE_GDBM
65     int no_db_lookup;
66 #endif
67 } Config;
68
69 typedef struct ServiceInfo ServiceInfo;
70
71 struct ServiceInfo {
72     AvahiIfIndex interface;
73     AvahiProtocol protocol;
74     char *name, *type, *domain;
75
76     AvahiServiceResolver *resolver;
77     Config *config;
78
79     AVAHI_LLIST_FIELDS(ServiceInfo, info);
80 };
81
82 static AvahiSimplePoll *simple_poll = NULL;
83 static AvahiClient *client = NULL;
84 static int n_all_for_now = 0, n_cache_exhausted = 0, n_resolving = 0;
85 static AvahiStringList *browsed_types = NULL;
86 static ServiceInfo *services = NULL;
87 static int n_columns = 80;
88
89 static void check_terminate(Config *c) {
90
91     assert(n_all_for_now >= 0);
92     assert(n_cache_exhausted >= 0);
93     assert(n_resolving >= 0);
94     
95     if (n_all_for_now <= 0 && n_resolving <= 0) {
96
97         if (c->verbose) {
98             printf(": All for now\n");
99             n_all_for_now++; /* Make sure that this event is not repeated */
100         }
101         
102         if (c->terminate_on_all_for_now)
103             avahi_simple_poll_quit(simple_poll);
104     }
105     
106     if (n_cache_exhausted <= 0 && n_resolving <= 0) {
107
108         if (c->verbose) {
109             printf(": Cache exhausted\n");
110             n_cache_exhausted++; /* Make sure that this event is not repeated */
111         }
112         
113         if (c->terminate_on_cache_exhausted)
114             avahi_simple_poll_quit(simple_poll);
115     }
116 }
117
118 static ServiceInfo *find_service(AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
119     ServiceInfo *i;
120
121     for (i = services; i; i = i->info_next)
122         if (i->interface == interface &&
123             i->protocol == protocol &&
124             strcasecmp(i->name, name) == 0 &&
125             avahi_domain_equal(i->type, type) == 0 &&
126             avahi_domain_equal(i->domain, domain) == 0)
127
128             return i;
129
130     return NULL;
131 }
132
133 static void print_service_line(Config *config, char c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
134     char ifname[IF_NAMESIZE];
135
136 #ifdef HAVE_GDBM
137     if (!config->no_db_lookup)
138         type = stdb_lookup(type);
139 #endif
140     
141     printf("%c %4s %4s %-*s %-20s %s\n",
142            c,
143            if_indextoname(interface, ifname), avahi_proto_to_string(protocol), 
144            n_columns-35, name, type, domain);
145 }
146
147 static void service_resolver_callback(
148     AvahiServiceResolver *r,
149     AvahiIfIndex interface,
150     AvahiProtocol protocol,
151     AvahiResolverEvent event,
152     const char *name,
153     const char *type,
154     const char *domain,
155     const char *host_name,
156     const AvahiAddress *a,
157     uint16_t port,
158     AvahiStringList *txt,
159     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
160     void *userdata) {
161     
162     ServiceInfo *i = userdata;
163     
164     assert(r);
165     assert(i);
166
167     switch (event) {
168         case AVAHI_RESOLVER_FOUND: {
169             char address[AVAHI_ADDRESS_STR_MAX], *t;
170
171             avahi_address_snprint(address, sizeof(address), a);
172
173             t = avahi_string_list_to_string(txt);
174
175             print_service_line(i->config, '=', interface, protocol, name, type, domain);
176             
177             printf("   hostname = [%s]\n"
178                    "   address = [%s]\n"
179                    "   port = [%i]\n"
180                    "   txt = [%s]\n",
181                    host_name,
182                    address,
183                    port,
184                    t);
185             avahi_free(t);
186
187             break;
188         }
189
190         case AVAHI_RESOLVER_FAILURE:
191             
192             fprintf(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
193             break;
194     }
195
196     
197     avahi_service_resolver_free(i->resolver);
198     i->resolver = NULL;
199
200     assert(n_resolving > 0);
201     n_resolving--;
202     check_terminate(i->config);
203 }
204
205 static ServiceInfo *add_service(Config *c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
206     ServiceInfo *i;
207
208     i = avahi_new(ServiceInfo, 1);
209
210     if (c->resolve) {
211         if (!(i->resolver = avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, service_resolver_callback, i))) {
212             avahi_free(i);
213             fprintf(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
214             return NULL;
215         }
216
217         n_resolving++;
218     } else
219         i->resolver = NULL;
220
221     i->interface = interface;
222     i->protocol = protocol;
223     i->name = avahi_strdup(name);
224     i->type = avahi_strdup(type);
225     i->domain = avahi_strdup(domain);
226     i->config = c;
227
228     AVAHI_LLIST_PREPEND(ServiceInfo, info, services, i);
229
230     return i;
231 }
232
233 static void remove_service(Config *c, ServiceInfo *i) {
234     assert(c);
235     assert(i);
236
237     AVAHI_LLIST_REMOVE(ServiceInfo, info, services, i);
238
239     if (i->resolver)
240         avahi_service_resolver_free(i->resolver);
241     
242     avahi_free(i->name);
243     avahi_free(i->type);
244     avahi_free(i->domain);
245     avahi_free(i);
246 }
247
248 static void service_browser_callback(
249     AvahiServiceBrowser *b,
250     AvahiIfIndex interface,
251     AvahiProtocol protocol,
252     AvahiBrowserEvent event,
253     const char *name,
254     const char *type,
255     const char *domain,
256     AvahiLookupResultFlags flags,
257     void *userdata) {
258
259     Config *c = userdata;
260     
261     assert(b);
262     assert(c);
263
264     switch (event) {
265         case AVAHI_BROWSER_NEW: {
266             if (c->ignore_local && (flags & AVAHI_LOOKUP_RESULT_LOCAL))
267                 break;
268
269             if (find_service(interface, protocol, name, type, domain))
270                 return;
271
272             add_service(c, interface, protocol, name, type, domain);
273
274             print_service_line(c, '+', interface, protocol, name, type, domain);
275             break;
276
277         }
278
279         case AVAHI_BROWSER_REMOVE: {
280             ServiceInfo *info;
281             
282             if (!(info = find_service(interface, protocol, name, type, domain)))
283                 return;
284
285             remove_service(c, info);
286             
287             print_service_line(c, '-', interface, protocol, name, type, domain);
288             break;
289         }
290             
291         case AVAHI_BROWSER_FAILURE:
292             fprintf(stderr, "service_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
293             avahi_simple_poll_quit(simple_poll);
294             break;
295
296         case AVAHI_BROWSER_CACHE_EXHAUSTED:
297             n_cache_exhausted --;
298             check_terminate(c);
299             break;
300             
301         case AVAHI_BROWSER_ALL_FOR_NOW:
302             n_all_for_now --;
303             check_terminate(c);
304             break;
305     }
306 }
307
308 static void browse_service_type(Config *c, const char *stype, const char *domain) {
309     AvahiServiceBrowser *b;
310     AvahiStringList *i;
311     
312     assert(c);
313     assert(client);
314     assert(stype);
315
316     for (i = browsed_types; i; i = i->next)
317         if (avahi_domain_equal(stype, (char*) i->text))
318             return;
319
320     if (!(b = avahi_service_browser_new(
321               client,
322               AVAHI_IF_UNSPEC,
323               AVAHI_PROTO_UNSPEC,
324               stype,
325               domain,
326               0,
327               service_browser_callback,
328               c))) {
329
330         fprintf(stderr, "avahi_service_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
331         avahi_simple_poll_quit(simple_poll);
332     }
333
334     browsed_types = avahi_string_list_add(browsed_types, stype);
335
336     n_all_for_now++;
337     n_cache_exhausted++;
338 }
339
340 static void service_type_browser_callback(
341     AvahiServiceTypeBrowser *b,
342     AVAHI_GCC_UNUSED AvahiIfIndex interface,
343     AVAHI_GCC_UNUSED AvahiProtocol protocol,
344     AvahiBrowserEvent event,
345     const char *type,
346     const char *domain,
347     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
348     void *userdata) {
349
350     Config *c = userdata;
351
352     assert(b);
353     assert(c);
354     
355     switch (event) {
356         
357         case AVAHI_BROWSER_NEW:
358             browse_service_type(c, type, domain);
359             break;
360
361         case AVAHI_BROWSER_REMOVE:
362             /* We're dirty and never remove the browser again */
363             break;
364
365         case AVAHI_BROWSER_FAILURE:
366             fprintf(stderr, "service_type_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
367             avahi_simple_poll_quit(simple_poll);
368             break;
369             
370         case AVAHI_BROWSER_CACHE_EXHAUSTED:
371             n_cache_exhausted --;
372             check_terminate(c);
373             break;
374             
375         case AVAHI_BROWSER_ALL_FOR_NOW:
376             n_all_for_now --;
377             check_terminate(c);
378             break;
379     }
380 }
381
382 static void browse_all(Config *c) {
383     AvahiServiceTypeBrowser *b;
384     
385     assert(c);
386
387     if (!(b = avahi_service_type_browser_new(
388               client,
389               AVAHI_IF_UNSPEC,
390               AVAHI_PROTO_UNSPEC,
391               c->domain,
392               0,
393               service_type_browser_callback,
394               c))) {
395         
396         fprintf(stderr, "avahi_service_type_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
397         avahi_simple_poll_quit(simple_poll);
398     }
399
400     n_cache_exhausted++;
401     n_all_for_now++;
402 }
403
404 static void client_callback(AVAHI_GCC_UNUSED AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
405     switch (state) {
406         case AVAHI_CLIENT_DISCONNECTED:
407             fprintf(stderr, "Client disconnected, exiting.\n");
408             avahi_simple_poll_quit(simple_poll);
409             break;
410
411         case AVAHI_CLIENT_S_REGISTERING:
412         case AVAHI_CLIENT_S_RUNNING:
413         case AVAHI_CLIENT_S_COLLISION:
414             ;
415     }
416 }
417
418 static void help(FILE *f, const char *argv0) {
419     fprintf(f,
420             "%s [options] <type>\n"
421             "%s [options] -a\n\n"
422             "    -h --help          Show this help\n"
423             "    -V --version       Show version\n"
424             "    -d --domain=DOMAIN The domain to browse\n"
425             "    -a --all           Show all services, regardless of the type\n"
426             "    -v --verbose       Enable verbose mode\n"
427             "    -t --terminate     Terminate after getting or more or less complete list\n"
428             "    -c --cache         Terminate after dumping all entries from the cache\n"
429             "    -l --ignore-local  Ignore local services\n"
430             "    -r --resolve       Resolve services found\n"
431 #ifdef HAVE_GDBM
432             "    -k --no-db-lookup  Don't lookup service types\n"
433 #endif
434
435             , argv0, argv0);
436 }
437
438 static int parse_command_line(Config *c, int argc, char *argv[]) {
439     int o;
440
441     static const struct option long_options[] = {
442         { "help",         no_argument,       NULL, 'h' },
443         { "version",      no_argument,       NULL, 'V' },
444         { "domain",       required_argument, NULL, 'd' },
445         { "all",          no_argument,       NULL, 'a' },
446         { "verbose",      no_argument,       NULL, 'v' },
447         { "terminate",    no_argument,       NULL, 't' },
448         { "cache",        no_argument,       NULL, 'c' },
449         { "ignore-local", no_argument,       NULL, 'l' },
450         { "resolve",      no_argument,       NULL, 'r' },
451 #ifdef HAVE_GDBM
452         { "no-db-lookup", no_argument,       NULL, 'k' },
453 #endif
454         { NULL, 0, NULL, 0 }
455     };
456
457     assert(c);
458
459     c->command = COMMAND_RUN;
460     c->verbose =
461         c->terminate_on_cache_exhausted =
462         c->terminate_on_all_for_now =
463         c->show_all =
464         c->ignore_local =
465         c->resolve = 0;
466     c->domain = c->stype = NULL;
467
468 #ifdef HAVE_GDBM
469     c->no_db_lookup = 0;
470 #endif
471     
472     opterr = 0;
473     while ((o = getopt_long(argc, argv, "hVd:avtclr"
474 #ifdef HAVE_GDBM
475                             "k"
476 #endif
477                             , long_options, NULL)) >= 0) {
478
479         switch(o) {
480             case 'h':
481                 c->command = COMMAND_HELP;
482                 break;
483             case 'V':
484                 c->command = COMMAND_VERSION;
485                 break;
486             case 'd':
487                 c->domain = avahi_strdup(optarg);
488                 break;
489             case 'a':
490                 c->show_all = 1;
491                 break;
492             case 'v':
493                 c->verbose = 1;
494                 break;
495             case 't':
496                 c->terminate_on_all_for_now = 1;
497                 break;
498             case 'c':
499                 c->terminate_on_cache_exhausted = 1;
500                 break;
501             case 'l':
502                 c->ignore_local = 1;
503                 break;
504             case 'r':
505                 c->resolve = 1;
506                 break;
507 #ifdef HAVE_GDBM
508             case 'k':
509                 c->no_db_lookup = 1;
510                 break;
511 #endif
512             default:
513                 fprintf(stderr, "Invalid command line argument: %c\n", o);
514                 return -1;
515         }
516     }
517
518     if (c->command == COMMAND_RUN && !c->show_all) {
519         if (optind >= argc) {
520             fprintf(stderr, "Too few arguments\n");
521             return -1;
522         }
523
524         c->stype = avahi_strdup(argv[optind]);
525         optind++;
526     }
527     
528     if (optind < argc) {
529         fprintf(stderr, "Too many arguments\n");
530         return -1;
531     }
532         
533     return 0;
534 }
535
536 int main(int argc, char *argv[]) {
537     int ret = 1, error;
538     Config config;
539     const char *argv0;
540     char *ec;
541
542     setlocale(LC_ALL, "");
543
544     if ((argv0 = strrchr(argv[0], '/')))
545         argv0++;
546     else
547         argv0 = argv[0];
548
549     if ((ec = getenv("COLUMNS")))
550         n_columns = atoi(ec);
551
552     if (n_columns < 40)
553         n_columns = 40;
554     
555     if (parse_command_line(&config, argc, argv) < 0)
556         goto fail;
557
558     switch (config.command) {
559         case COMMAND_HELP:
560             help(stdout, argv0);
561             ret = 0;
562             break;
563             
564         case COMMAND_VERSION:
565             printf("%s "PACKAGE_VERSION"\n", argv0);
566             ret = 0;
567             break;
568
569         case COMMAND_RUN:
570             
571             if (!(simple_poll = avahi_simple_poll_new())) {
572                 fprintf(stderr, "Failed to create simple poll object.\n");
573                 goto fail;
574             }
575             
576             if (sigint_install(simple_poll) < 0)
577                 goto fail;
578             
579             if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), client_callback, NULL, &error))) {
580                 fprintf(stderr, "Failed to create client object: %s\n", avahi_strerror(error));
581                 goto fail;
582             }
583
584             if (config.verbose) {
585                 const char *version, *hn;
586
587                 if (!(version = avahi_client_get_version_string(client))) {
588                     fprintf(stderr, "Failed to query version string: %s\n", avahi_strerror(avahi_client_errno(client)));
589                     goto fail;
590                 }
591
592                 if (!(hn = avahi_client_get_host_name_fqdn(client))) {
593                     fprintf(stderr, "Failed to query host name: %s\n", avahi_strerror(avahi_client_errno(client)));
594                     goto fail;
595                 }
596                 
597                 fprintf(stderr, "Server version: %s; Host name: %s\n\n", version, hn);
598                 fprintf(stderr, "E Ifce Prot %-*s %-20s Domain\n", n_columns-35, "Name", "Type");
599             }
600             
601             if (config.show_all)
602                 browse_all(&config);
603             else
604                 browse_service_type(&config, config.stype, config.domain);
605             
606             avahi_simple_poll_loop(simple_poll);
607             ret = 0;
608             break;
609     }
610     
611     
612 fail:
613
614     while (services)
615         remove_service(&config, services);
616
617     if (client)
618         avahi_client_free(client);
619
620     sigint_uninstall();
621     
622     if (simple_poll)
623         avahi_simple_poll_free(simple_poll);
624
625     avahi_free(config.domain);
626     avahi_free(config.stype);
627
628     avahi_string_list_free(browsed_types);
629
630 #ifdef HAVE_GDBM
631     stdb_shutdown();
632 #endif    
633
634     return ret;
635 }