]> git.meshlink.io Git - catta/blob - avahi-utils/avahi-browse.c
* add new flags parameter to avahi_client_new()
[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 <sys/types.h>
32 #include <sys/socket.h>
33 #include <net/if.h>
34 #include <locale.h>
35
36 #include <avahi-common/simple-watch.h>
37 #include <avahi-common/error.h>
38 #include <avahi-common/malloc.h>
39 #include <avahi-common/domain.h>
40 #include <avahi-common/llist.h>
41 #include <avahi-client/client.h>
42 #include <avahi-client/lookup.h>
43
44 #include "sigint.h"
45
46 #ifdef HAVE_GDBM
47 #include "stdb.h"
48 #endif
49
50 typedef enum {
51     COMMAND_HELP,
52     COMMAND_VERSION,
53     COMMAND_BROWSE_SERVICES,
54     COMMAND_BROWSE_ALL_SERVICES,
55     COMMAND_BROWSE_DOMAINS
56 } Command;
57
58 typedef struct Config {
59     int verbose;
60     int terminate_on_all_for_now;
61     int terminate_on_cache_exhausted;
62     char *domain;
63     char *stype;
64     int ignore_local;
65     Command command;
66     int resolve;
67     int no_fail;
68 #ifdef HAVE_GDBM
69     int no_db_lookup;
70 #endif
71 } Config;
72
73 typedef struct ServiceInfo ServiceInfo;
74
75 struct ServiceInfo {
76     AvahiIfIndex interface;
77     AvahiProtocol protocol;
78     char *name, *type, *domain;
79
80     AvahiServiceResolver *resolver;
81     Config *config;
82
83     AVAHI_LLIST_FIELDS(ServiceInfo, info);
84 };
85
86 static AvahiSimplePoll *simple_poll = NULL;
87 static AvahiClient *client = NULL;
88 static int n_all_for_now = 0, n_cache_exhausted = 0, n_resolving = 0;
89 static AvahiStringList *browsed_types = NULL;
90 static ServiceInfo *services = NULL;
91 static int n_columns = 80;
92 static int browsing = 0;
93
94 static void check_terminate(Config *c) {
95
96     assert(n_all_for_now >= 0);
97     assert(n_cache_exhausted >= 0);
98     assert(n_resolving >= 0);
99     
100     if (n_all_for_now <= 0 && n_resolving <= 0) {
101
102         if (c->verbose) {
103             printf(": All for now\n");
104             n_all_for_now++; /* Make sure that this event is not repeated */
105         }
106         
107         if (c->terminate_on_all_for_now)
108             avahi_simple_poll_quit(simple_poll);
109     }
110     
111     if (n_cache_exhausted <= 0 && n_resolving <= 0) {
112
113         if (c->verbose) {
114             printf(": Cache exhausted\n");
115             n_cache_exhausted++; /* Make sure that this event is not repeated */
116         }
117         
118         if (c->terminate_on_cache_exhausted)
119             avahi_simple_poll_quit(simple_poll);
120     }
121 }
122
123 static ServiceInfo *find_service(AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
124     ServiceInfo *i;
125
126     for (i = services; i; i = i->info_next)
127         if (i->interface == interface &&
128             i->protocol == protocol &&
129             strcasecmp(i->name, name) == 0 &&
130             avahi_domain_equal(i->type, type) == 0 &&
131             avahi_domain_equal(i->domain, domain) == 0)
132
133             return i;
134
135     return NULL;
136 }
137
138 static void print_service_line(Config *config, char c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
139     char ifname[IF_NAMESIZE];
140
141 #ifdef HAVE_GDBM
142     if (!config->no_db_lookup)
143         type = stdb_lookup(type);
144 #endif
145     
146     printf("%c %4s %4s %-*s %-20s %s\n",
147            c,
148            interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : "n/a",
149            protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "n/a", 
150            n_columns-35, name, type, domain);
151 }
152
153 static void service_resolver_callback(
154     AvahiServiceResolver *r,
155     AvahiIfIndex interface,
156     AvahiProtocol protocol,
157     AvahiResolverEvent event,
158     const char *name,
159     const char *type,
160     const char *domain,
161     const char *host_name,
162     const AvahiAddress *a,
163     uint16_t port,
164     AvahiStringList *txt,
165     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
166     void *userdata) {
167     
168     ServiceInfo *i = userdata;
169     
170     assert(r);
171     assert(i);
172
173     switch (event) {
174         case AVAHI_RESOLVER_FOUND: {
175             char address[AVAHI_ADDRESS_STR_MAX], *t;
176
177             avahi_address_snprint(address, sizeof(address), a);
178
179             t = avahi_string_list_to_string(txt);
180
181             print_service_line(i->config, '=', interface, protocol, name, type, domain);
182             
183             printf("   hostname = [%s]\n"
184                    "   address = [%s]\n"
185                    "   port = [%i]\n"
186                    "   txt = [%s]\n",
187                    host_name,
188                    address,
189                    port,
190                    t);
191             avahi_free(t);
192
193             break;
194         }
195
196         case AVAHI_RESOLVER_FAILURE:
197             
198             fprintf(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
199             break;
200     }
201
202     
203     avahi_service_resolver_free(i->resolver);
204     i->resolver = NULL;
205
206     assert(n_resolving > 0);
207     n_resolving--;
208     check_terminate(i->config);
209 }
210
211 static ServiceInfo *add_service(Config *c, AvahiIfIndex interface, AvahiProtocol protocol, const char *name, const char *type, const char *domain) {
212     ServiceInfo *i;
213
214     i = avahi_new(ServiceInfo, 1);
215
216     if (c->resolve) {
217         if (!(i->resolver = avahi_service_resolver_new(client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, service_resolver_callback, i))) {
218             avahi_free(i);
219             fprintf(stderr, "Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_client_errno(client)));
220             return NULL;
221         }
222
223         n_resolving++;
224     } else
225         i->resolver = NULL;
226
227     i->interface = interface;
228     i->protocol = protocol;
229     i->name = avahi_strdup(name);
230     i->type = avahi_strdup(type);
231     i->domain = avahi_strdup(domain);
232     i->config = c;
233
234     AVAHI_LLIST_PREPEND(ServiceInfo, info, services, i);
235
236     return i;
237 }
238
239 static void remove_service(Config *c, ServiceInfo *i) {
240     assert(c);
241     assert(i);
242
243     AVAHI_LLIST_REMOVE(ServiceInfo, info, services, i);
244
245     if (i->resolver)
246         avahi_service_resolver_free(i->resolver);
247     
248     avahi_free(i->name);
249     avahi_free(i->type);
250     avahi_free(i->domain);
251     avahi_free(i);
252 }
253
254 static void service_browser_callback(
255     AvahiServiceBrowser *b,
256     AvahiIfIndex interface,
257     AvahiProtocol protocol,
258     AvahiBrowserEvent event,
259     const char *name,
260     const char *type,
261     const char *domain,
262     AvahiLookupResultFlags flags,
263     void *userdata) {
264
265     Config *c = userdata;
266     
267     assert(b);
268     assert(c);
269
270     switch (event) {
271         case AVAHI_BROWSER_NEW: {
272             if (c->ignore_local && (flags & AVAHI_LOOKUP_RESULT_LOCAL))
273                 break;
274
275             if (find_service(interface, protocol, name, type, domain))
276                 return;
277
278             add_service(c, interface, protocol, name, type, domain);
279
280             print_service_line(c, '+', interface, protocol, name, type, domain);
281             break;
282
283         }
284
285         case AVAHI_BROWSER_REMOVE: {
286             ServiceInfo *info;
287             
288             if (!(info = find_service(interface, protocol, name, type, domain)))
289                 return;
290
291             remove_service(c, info);
292             
293             print_service_line(c, '-', interface, protocol, name, type, domain);
294             break;
295         }
296             
297         case AVAHI_BROWSER_FAILURE:
298             fprintf(stderr, "service_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
299             avahi_simple_poll_quit(simple_poll);
300             break;
301
302         case AVAHI_BROWSER_CACHE_EXHAUSTED:
303             n_cache_exhausted --;
304             check_terminate(c);
305             break;
306             
307         case AVAHI_BROWSER_ALL_FOR_NOW:
308             n_all_for_now --;
309             check_terminate(c);
310             break;
311     }
312 }
313
314 static void browse_service_type(Config *c, const char *stype, const char *domain) {
315     AvahiServiceBrowser *b;
316     AvahiStringList *i;
317     
318     assert(c);
319     assert(client);
320     assert(stype);
321
322     for (i = browsed_types; i; i = i->next)
323         if (avahi_domain_equal(stype, (char*) i->text))
324             return;
325
326     if (!(b = avahi_service_browser_new(
327               client,
328               AVAHI_IF_UNSPEC,
329               AVAHI_PROTO_UNSPEC,
330               stype,
331               domain,
332               0,
333               service_browser_callback,
334               c))) {
335
336         fprintf(stderr, "avahi_service_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
337         avahi_simple_poll_quit(simple_poll);
338     }
339
340     browsed_types = avahi_string_list_add(browsed_types, stype);
341
342     n_all_for_now++;
343     n_cache_exhausted++;
344 }
345
346 static void service_type_browser_callback(
347     AvahiServiceTypeBrowser *b,
348     AVAHI_GCC_UNUSED AvahiIfIndex interface,
349     AVAHI_GCC_UNUSED AvahiProtocol protocol,
350     AvahiBrowserEvent event,
351     const char *type,
352     const char *domain,
353     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
354     void *userdata) {
355
356     Config *c = userdata;
357
358     assert(b);
359     assert(c);
360     
361     switch (event) {
362         
363         case AVAHI_BROWSER_NEW:
364             browse_service_type(c, type, domain);
365             break;
366
367         case AVAHI_BROWSER_REMOVE:
368             /* We're dirty and never remove the browser again */
369             break;
370
371         case AVAHI_BROWSER_FAILURE:
372             fprintf(stderr, "service_type_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
373             avahi_simple_poll_quit(simple_poll);
374             break;
375             
376         case AVAHI_BROWSER_CACHE_EXHAUSTED:
377             n_cache_exhausted --;
378             check_terminate(c);
379             break;
380             
381         case AVAHI_BROWSER_ALL_FOR_NOW:
382             n_all_for_now --;
383             check_terminate(c);
384             break;
385     }
386 }
387
388 static void browse_all(Config *c) {
389     AvahiServiceTypeBrowser *b;
390     
391     assert(c);
392
393     if (!(b = avahi_service_type_browser_new(
394               client,
395               AVAHI_IF_UNSPEC,
396               AVAHI_PROTO_UNSPEC,
397               c->domain,
398               0,
399               service_type_browser_callback,
400               c))) {
401         
402         fprintf(stderr, "avahi_service_type_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
403         avahi_simple_poll_quit(simple_poll);
404     }
405
406     n_cache_exhausted++;
407     n_all_for_now++;
408 }
409
410 static void domain_browser_callback(
411     AvahiDomainBrowser *b,
412     AVAHI_GCC_UNUSED AvahiIfIndex interface,
413     AVAHI_GCC_UNUSED AvahiProtocol protocol,
414     AvahiBrowserEvent event,
415     const char *domain,
416     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
417     void *userdata) {
418
419     Config *c = userdata;
420
421     assert(b);
422     assert(c);
423     
424     switch (event) {
425         
426         case AVAHI_BROWSER_NEW:
427         case AVAHI_BROWSER_REMOVE: {
428             char ifname[IF_NAMESIZE];
429             
430             printf("%c %4s %4s %s\n",
431                    event == AVAHI_BROWSER_NEW ? '+' : '-',
432                    interface != AVAHI_IF_UNSPEC ? if_indextoname(interface, ifname) : "n/a",
433                    protocol != AVAHI_PROTO_UNSPEC ? avahi_proto_to_string(protocol) : "n/a", 
434                    domain);
435             break;
436         }
437
438         case AVAHI_BROWSER_FAILURE:
439             fprintf(stderr, "domain_browser failed: %s\n", avahi_strerror(avahi_client_errno(client)));
440             avahi_simple_poll_quit(simple_poll);
441             break;
442             
443         case AVAHI_BROWSER_CACHE_EXHAUSTED:
444             n_cache_exhausted --;
445             check_terminate(c);
446             break;
447             
448         case AVAHI_BROWSER_ALL_FOR_NOW:
449             n_all_for_now --;
450             check_terminate(c);
451             break;
452     }
453 }
454
455 static void browse_domains(Config *c) {
456     AvahiDomainBrowser *b;
457
458     assert(c);
459
460     if (!(b = avahi_domain_browser_new(
461               client,
462               AVAHI_IF_UNSPEC,
463               AVAHI_PROTO_UNSPEC,
464               c->domain,
465               AVAHI_DOMAIN_BROWSER_BROWSE,
466               0,
467               domain_browser_callback,
468               c))) {
469
470         fprintf(stderr, "avahi_domain_browser_new() failed: %s\n", avahi_strerror(avahi_client_errno(client)));
471         avahi_simple_poll_quit(simple_poll);
472     }
473
474     n_cache_exhausted++;
475     n_all_for_now++;
476 }
477
478 static int start(Config *config) {
479
480     assert(!browsing);
481     
482     if (config->verbose) {
483         const char *version, *hn;
484         
485         if (!(version = avahi_client_get_version_string(client))) {
486             fprintf(stderr, "Failed to query version string: %s\n", avahi_strerror(avahi_client_errno(client)));
487             return -1;
488         }
489         
490         if (!(hn = avahi_client_get_host_name_fqdn(client))) {
491             fprintf(stderr, "Failed to query host name: %s\n", avahi_strerror(avahi_client_errno(client)));
492             return -1;
493         }
494         
495         fprintf(stderr, "Server version: %s; Host name: %s\n", version, hn);
496         
497         if (config->command == COMMAND_BROWSE_DOMAINS)
498             fprintf(stderr, "E Ifce Prot Domain\n");
499         else
500             fprintf(stderr, "E Ifce Prot %-*s %-20s Domain\n", n_columns-35, "Name", "Type");
501     }
502     
503     if (config->command == COMMAND_BROWSE_SERVICES)
504         browse_service_type(config, config->stype, config->domain);
505     else if (config->command == COMMAND_BROWSE_ALL_SERVICES)
506         browse_all(config);
507     else {
508         assert(config->command == COMMAND_BROWSE_DOMAINS);
509         browse_domains(config);
510     }
511
512     browsing = 1;
513     return 0;
514 }
515     
516 static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
517     Config *config = userdata;
518
519     /* This function might be called when avahi_client_new() has not
520      * returned yet.*/
521     client = c;
522     
523     switch (state) {
524         case AVAHI_CLIENT_FAILURE:
525             
526             if (config->no_fail && avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) {
527                 int error;
528
529                 /* We have been disconnected, so let reconnect */
530
531                 fprintf(stderr, "Disconnected, reconnecting ...\n");
532
533                 avahi_client_free(client);
534                 client = NULL;
535
536                 avahi_string_list_free(browsed_types);
537                 browsed_types = NULL;
538                 
539                 while (services)
540                     remove_service(config, services);
541
542                 browsing = 0;
543
544                 if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), AVAHI_CLIENT_NO_FAIL, client_callback, config, &error))) {
545                     fprintf(stderr, "Failed to create client object: %s\n", avahi_strerror(error));
546                     avahi_simple_poll_quit(simple_poll);
547                 }
548
549             } else {
550                 fprintf(stderr, "Client failure, exiting: %s\n", avahi_strerror(avahi_client_errno(c)));
551                 avahi_simple_poll_quit(simple_poll);
552             }
553             
554             break;
555             
556         case AVAHI_CLIENT_S_REGISTERING:
557         case AVAHI_CLIENT_S_RUNNING:
558         case AVAHI_CLIENT_S_COLLISION:
559
560             if (!browsing)
561                 if (start(config) < 0)
562                     avahi_simple_poll_quit(simple_poll);
563
564             break;
565             
566         case AVAHI_CLIENT_CONNECTING:
567             
568             if (config->verbose)
569                 fprintf(stderr, "Waiting for daemon ...\n");
570
571                 break;
572     }
573 }
574
575 static void help(FILE *f, const char *argv0) {
576     fprintf(f,
577             "%s [options] <service type>\n"
578             "%s [options] -a\n"
579             "%s [options] -D\n\n"
580             "    -h --help            Show this help\n"
581             "    -V --version         Show version\n"
582             "    -D --browse-domains  Browse for browsing domains instead of services\n"
583             "    -a --all             Show all services, regardless of the type\n"
584             "    -d --domain=DOMAIN   The domain to browse in\n"
585             "    -v --verbose         Enable verbose mode\n"
586             "    -t --terminate       Terminate after dumping a more or less complete list\n"
587             "    -c --cache           Terminate after dumping all entries from the cache\n"
588             "    -l --ignore-local    Ignore local services\n"
589             "    -r --resolve         Resolve services found\n"
590             "    -f --no-fail         Don't fail if the server is not available\n"
591 #ifdef HAVE_GDBM
592             "    -k --no-db-lookup    Don't lookup service types\n"
593 #endif
594             , argv0, argv0, argv0);
595 }
596
597 static int parse_command_line(Config *c, int argc, char *argv[]) {
598     int o;
599
600     static const struct option long_options[] = {
601         { "help",           no_argument,       NULL, 'h' },
602         { "version",        no_argument,       NULL, 'V' },
603         { "browse-domains", no_argument,       NULL, 'D' },
604         { "domain",         required_argument, NULL, 'd' },
605         { "all",            no_argument,       NULL, 'a' },
606         { "verbose",        no_argument,       NULL, 'v' },
607         { "terminate",      no_argument,       NULL, 't' },
608         { "cache",          no_argument,       NULL, 'c' },
609         { "ignore-local",   no_argument,       NULL, 'l' },
610         { "resolve",        no_argument,       NULL, 'r' },
611         { "no-fail",        no_argument,       NULL, 'f' },
612 #ifdef HAVE_GDBM
613         { "no-db-lookup",   no_argument,       NULL, 'k' },
614 #endif
615         { NULL, 0, NULL, 0 }
616     };
617
618     assert(c);
619
620     c->command = COMMAND_BROWSE_SERVICES;
621     c->verbose =
622         c->terminate_on_cache_exhausted =
623         c->terminate_on_all_for_now =
624         c->ignore_local =
625         c->resolve =
626         c->no_fail = 0;
627     c->domain = c->stype = NULL;
628
629 #ifdef HAVE_GDBM
630     c->no_db_lookup = 0;
631 #endif
632     
633     opterr = 0;
634     while ((o = getopt_long(argc, argv, "hVd:avtclrDf"
635 #ifdef HAVE_GDBM
636                             "k"
637 #endif
638                             , long_options, NULL)) >= 0) {
639
640         switch(o) {
641             case 'h':
642                 c->command = COMMAND_HELP;
643                 break;
644             case 'V':
645                 c->command = COMMAND_VERSION;
646                 break;
647             case 'a':
648                 c->command = COMMAND_BROWSE_ALL_SERVICES;
649                 break;
650             case 'D':
651                 c->command = COMMAND_BROWSE_DOMAINS;
652                 break;
653             case 'd':
654                 c->domain = avahi_strdup(optarg);
655                 break;
656             case 'v':
657                 c->verbose = 1;
658                 break;
659             case 't':
660                 c->terminate_on_all_for_now = 1;
661                 break;
662             case 'c':
663                 c->terminate_on_cache_exhausted = 1;
664                 break;
665             case 'l':
666                 c->ignore_local = 1;
667                 break;
668             case 'r':
669                 c->resolve = 1;
670                 break;
671             case 'f':
672                 c->no_fail = 1;
673                 break;
674 #ifdef HAVE_GDBM
675             case 'k':
676                 c->no_db_lookup = 1;
677                 break;
678 #endif
679             default:
680                 fprintf(stderr, "Invalid command line argument: %c\n", o);
681                 return -1;
682         }
683     }
684
685     if (c->command == COMMAND_BROWSE_SERVICES) {
686         if (optind >= argc) {
687             fprintf(stderr, "Too few arguments\n");
688             return -1;
689         }
690
691         c->stype = avahi_strdup(argv[optind]);
692         optind++;
693     }
694     
695     if (optind < argc) {
696         fprintf(stderr, "Too many arguments\n");
697         return -1;
698     }
699         
700     return 0;
701 }
702
703 int main(int argc, char *argv[]) {
704     int ret = 1, error;
705     Config config;
706     const char *argv0;
707     char *ec;
708
709     setlocale(LC_ALL, "");
710
711     if ((argv0 = strrchr(argv[0], '/')))
712         argv0++;
713     else
714         argv0 = argv[0];
715
716     if ((ec = getenv("COLUMNS")))
717         n_columns = atoi(ec);
718
719     if (n_columns < 40)
720         n_columns = 40;
721     
722     if (parse_command_line(&config, argc, argv) < 0)
723         goto fail;
724
725     switch (config.command) {
726         case COMMAND_HELP:
727             help(stdout, argv0);
728             ret = 0;
729             break;
730             
731         case COMMAND_VERSION:
732             printf("%s "PACKAGE_VERSION"\n", argv0);
733             ret = 0;
734             break;
735
736         case COMMAND_BROWSE_SERVICES:
737         case COMMAND_BROWSE_ALL_SERVICES:
738         case COMMAND_BROWSE_DOMAINS:
739             
740             if (!(simple_poll = avahi_simple_poll_new())) {
741                 fprintf(stderr, "Failed to create simple poll object.\n");
742                 goto fail;
743             }
744             
745             if (sigint_install(simple_poll) < 0)
746                 goto fail;
747             
748             if (!(client = avahi_client_new(avahi_simple_poll_get(simple_poll), config.no_fail ? AVAHI_CLIENT_NO_FAIL : 0, client_callback, &config, &error))) {
749                 fprintf(stderr, "Failed to create client object: %s\n", avahi_strerror(error));
750                 goto fail;
751             }
752             
753             avahi_simple_poll_loop(simple_poll);
754             ret = 0;
755             break;
756     }
757     
758     
759 fail:
760
761     while (services)
762         remove_service(&config, services);
763
764     if (client)
765         avahi_client_free(client);
766
767     sigint_uninstall();
768     
769     if (simple_poll)
770         avahi_simple_poll_free(simple_poll);
771
772     avahi_free(config.domain);
773     avahi_free(config.stype);
774
775     avahi_string_list_free(browsed_types);
776
777 #ifdef HAVE_GDBM
778     stdb_shutdown();
779 #endif    
780
781     return ret;
782 }