]> git.ipfire.org Git - thirdparty/cups.git/blame - test/ippdiscover.c
Remove all of the Subversion keywords from various source files.
[thirdparty/cups.git] / test / ippdiscover.c
CommitLineData
06399b6e 1/*
503b54c9 2 * ippdiscover command for CUPS.
06399b6e 3 *
503b54c9
MS
4 * Copyright 2007-2013 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
06399b6e 6 *
503b54c9
MS
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * file is missing or damaged, see the license at "http://www.cups.org/".
06399b6e 12 *
503b54c9 13 * This file is subject to the Apple OS-Developed Software exception.
06399b6e
MS
14 */
15
16
17/*
18 * Include necessary headers.
19 */
20
21#include <cups/cups-private.h>
22#ifdef HAVE_DNSSD
23# include <dns_sd.h>
24# ifdef WIN32
25# pragma comment(lib, "dnssd.lib")
26# endif /* WIN32 */
27#endif /* HAVE_DNSSD */
28#ifdef HAVE_AVAHI
29# include <avahi-client/client.h>
30# include <avahi-client/lookup.h>
31# include <avahi-common/simple-watch.h>
32# include <avahi-common/domain.h>
33# include <avahi-common/error.h>
34# include <avahi-common/malloc.h>
35#define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX
36#endif /* HAVE_AVAHI */
37
38
39/*
40 * Local globals...
41 */
42
43#ifdef HAVE_AVAHI
44static int got_data = 0; /* Got data from poll? */
45static AvahiSimplePoll *simple_poll = NULL;
46 /* Poll information */
47#endif /* HAVE_AVAHI */
48static const char *program = NULL;/* Program to run */
49
50
51/*
52 * Local functions...
53 */
54
55#ifdef HAVE_DNSSD
56static void DNSSD_API browse_callback(DNSServiceRef sdRef,
57 DNSServiceFlags flags,
58 uint32_t interfaceIndex,
59 DNSServiceErrorType errorCode,
60 const char *serviceName,
61 const char *regtype,
62 const char *replyDomain, void *context)
63 __attribute__((nonnull(1,5,6,7,8)));
64static void DNSSD_API resolve_cb(DNSServiceRef sdRef,
65 DNSServiceFlags flags,
66 uint32_t interfaceIndex,
67 DNSServiceErrorType errorCode,
68 const char *fullName,
69 const char *hostTarget,
70 uint16_t port, uint16_t txtLen,
71 const unsigned char *txtRecord,
72 void *context);
73#endif /* HAVE_DNSSD */
74
75#ifdef HAVE_AVAHI
76static void browse_callback(AvahiServiceBrowser *browser,
77 AvahiIfIndex interface,
78 AvahiProtocol protocol,
79 AvahiBrowserEvent event,
80 const char *serviceName,
81 const char *regtype,
82 const char *replyDomain,
83 AvahiLookupResultFlags flags,
84 void *context);
85static void client_cb(AvahiClient *client, AvahiClientState state,
86 void *simple_poll);
87static int poll_cb(struct pollfd *pollfds, unsigned int num_pollfds,
88 int timeout, void *context);
89static void resolve_cb(AvahiServiceResolver *resolver,
90 AvahiIfIndex interface,
91 AvahiProtocol protocol,
92 AvahiResolverEvent event,
93 const char *name, const char *type,
94 const char *domain, const char *host_name,
95 const AvahiAddress *address, uint16_t port,
96 AvahiStringList *txt,
97 AvahiLookupResultFlags flags, void *context);
98#endif /* HAVE_AVAHI */
99
100static void resolve_and_run(const char *name, const char *type,
101 const char *domain);
102static void unquote(char *dst, const char *src, size_t dstsize);
103static void usage(void) __attribute__((noreturn));
104
105
106/*
107 * 'main()' - Browse for printers and run the specified command.
108 */
109
110int /* O - Exit status */
111main(int argc, /* I - Number of command-line args */
112 char *argv[]) /* I - Command-line arguments */
113{
114 int i; /* Looping var */
115 const char *opt, /* Current option character */
116 *name = NULL, /* Service name */
117 *type = "_ipp._tcp", /* Service type */
118 *domain = "local."; /* Service domain */
119#ifdef HAVE_DNSSD
120 DNSServiceRef ref; /* Browsing service reference */
121#endif /* HAVE_DNSSD */
122#ifdef HAVE_AVAHI
123 AvahiClient *client; /* Client information */
124 int error; /* Error code, if any */
125#endif /* HAVE_AVAHI */
126
127
128 for (i = 1; i < argc; i ++)
129 if (!strcmp(argv[i], "snmp"))
130 snmponly = 1;
131 else if (!strcmp(argv[i], "ipp"))
132 ipponly = 1;
133 else
134 {
135 puts("Usage: ./ipp-printers [{ipp | snmp}]");
136 return (1);
137 }
138
139 /*
140 * Create an array to track devices...
141 */
142
143 devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL);
144
145 /*
146 * Browse for different kinds of printers...
147 */
148
149 if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError)
150 {
151 perror("ERROR: Unable to create service connection");
152 return (1);
153 }
154
155 fd = DNSServiceRefSockFD(main_ref);
156
157 ipp_ref = main_ref;
158 DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0,
159 "_ipp._tcp", NULL, browse_callback, devices);
160
161 /*
162 * Loop until we are killed...
163 */
164
165 progress();
166
167 for (;;)
168 {
169 FD_ZERO(&input);
170 FD_SET(fd, &input);
171
172 timeout.tv_sec = 2;
173 timeout.tv_usec = 500000;
174
175 if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0)
176 {
177 time_t curtime = time(NULL);
178
179 for (device = (cups_device_t *)cupsArrayFirst(devices);
180 device;
181 device = (cups_device_t *)cupsArrayNext(devices))
182 if (!device->got_resolve)
183 {
184 if (!device->ref)
185 break;
186
187 if ((curtime - device->resolve_time) > 10)
188 {
189 device->got_resolve = -1;
190 fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n",
191 device->name);
192 progress();
193 }
194 else
195 break;
196 }
197
198 if (!device)
199 break;
200 }
201
202 if (FD_ISSET(fd, &input))
203 {
204 /*
205 * Process results of our browsing...
206 */
207
208 progress();
209 DNSServiceProcessResult(main_ref);
210 }
211 else
212 {
213 /*
214 * Query any devices we've found...
215 */
216
217 DNSServiceErrorType status; /* DNS query status */
218 int count; /* Number of queries */
219
220
221 for (device = (cups_device_t *)cupsArrayFirst(devices), count = 0;
222 device;
223 device = (cups_device_t *)cupsArrayNext(devices))
224 {
225 if (!device->ref && !device->sent)
226 {
227 /*
228 * Found the device, now get the TXT record(s) for it...
229 */
230
231 if (count < 50)
232 {
233 device->resolve_time = time(NULL);
234 device->ref = main_ref;
235
236 status = DNSServiceResolve(&(device->ref),
237 kDNSServiceFlagsShareConnection,
238 0, device->name, device->regtype,
239 device->domain, resolve_callback,
240 device);
241 if (status != kDNSServiceErr_NoError)
242 {
243 fprintf(stderr, "\rUnable to resolve \"%s\": %d\n",
244 device->name, status);
245 progress();
246 }
247 else
248 count ++;
249 }
250 }
251 else if (!device->sent && device->got_resolve)
252 {
253 /*
254 * Got the TXT records, now report the device...
255 */
256
257 DNSServiceRefDeallocate(device->ref);
258 device->ref = 0;
259 device->sent = 1;
260 }
261 }
262 }
263 }
264
265#ifndef DEBUG
266 fprintf(stderr, "\rFound %d printers. Now querying for capabilities...\n",
267 cupsArrayCount(devices));
268#endif /* !DEBUG */
269
270 puts("#!/bin/sh -x");
271 puts("test -d results && rm -rf results");
272 puts("mkdir results");
273 puts("CUPS_DEBUG_LEVEL=6; export CUPS_DEBUG_LEVEL");
274 puts("CUPS_DEBUG_FILTER='^(ipp|http|_ipp|_http|cupsGetResponse|cupsSend|"
275 "cupsWrite|cupsDo).*'; export CUPS_DEBUG_FILTER");
276
277 for (device = (cups_device_t *)cupsArrayFirst(devices);
278 device;
279 device = (cups_device_t *)cupsArrayNext(devices))
280 {
281 if (device->got_resolve <= 0 || device->cups_shared)
282 continue;
283
284#ifdef DEBUG
285 fprintf(stderr, "Checking \"%s\" (got_resolve=%d, cups_shared=%d, uri=%s)\n",
286 device->name, device->got_resolve, device->cups_shared, device->uri);
287#else
288 fprintf(stderr, "Checking \"%s\"...\n", device->name);
289#endif /* DEBUG */
290
291 if ((http = httpConnect(device->host, device->port)) == NULL)
292 {
293 fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name,
294 cupsLastErrorString());
295 continue;
296 }
297
298 request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES);
299 ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL,
300 device->uri);
301
302 response = cupsDoRequest(http, request, device->rp);
303
304 if (cupsLastError() > IPP_OK_SUBST)
305 fprintf(stderr, "Failed to query \"%s\": %s\n", device->name,
306 cupsLastErrorString());
307 else
308 {
309 if ((attr = ippFindAttribute(response, "ipp-versions-supported",
310 IPP_TAG_KEYWORD)) != NULL)
311 {
312 version = attr->values[0].string.text;
313
314 for (i = 1; i < attr->num_values; i ++)
315 if (strcmp(attr->values[i].string.text, version) > 0)
316 version = attr->values[i].string.text;
317 }
318 else
319 version = "1.0";
320
321 testfile = NULL;
322
323 if ((attr = ippFindAttribute(response, "document-format-supported",
324 IPP_TAG_MIMETYPE)) != NULL)
325 {
326 /*
327 * Figure out the test file for printing, preferring PDF and PostScript
328 * over JPEG and plain text...
329 */
330
331 for (i = 0; i < attr->num_values; i ++)
332 {
333 if (!strcasecmp(attr->values[i].string.text, "application/pdf"))
334 {
335 testfile = "testfile.pdf";
336 break;
337 }
338 else if (!strcasecmp(attr->values[i].string.text,
339 "application/postscript"))
340 testfile = "testfile.ps";
341 else if (!strcasecmp(attr->values[i].string.text, "image/jpeg") &&
342 !testfile)
343 testfile = "testfile.jpg";
344 else if (!strcasecmp(attr->values[i].string.text, "text/plain") &&
345 !testfile)
346 testfile = "testfile.txt";
347 else if (!strcasecmp(attr->values[i].string.text,
348 "application/vnd.hp-PCL") && !testfile)
349 testfile = "testfile.pcl";
350 }
351
352 if (!testfile)
353 {
354 fprintf(stderr,
355 "Printer \"%s\" reports the following IPP file formats:\n",
356 device->name);
357 for (i = 0; i < attr->num_values; i ++)
358 fprintf(stderr, " \"%s\"\n", attr->values[i].string.text);
359 }
360 }
361
362 if (!testfile && device->pdl)
363 {
364 char *pdl, /* Copy of pdl string */
365 *start, *end; /* Pointers into pdl string */
366
367
368 pdl = strdup(device->pdl);
369 for (start = device->pdl; start && *start; start = end)
370 {
371 if ((end = strchr(start, ',')) != NULL)
372 *end++ = '\0';
373
374 if (!strcasecmp(start, "application/pdf"))
375 {
376 testfile = "testfile.pdf";
377 break;
378 }
379 else if (!strcasecmp(start, "application/postscript"))
380 testfile = "testfile.ps";
381 else if (!strcasecmp(start, "image/jpeg") && !testfile)
382 testfile = "testfile.jpg";
383 else if (!strcasecmp(start, "text/plain") && !testfile)
384 testfile = "testfile.txt";
385 else if (!strcasecmp(start, "application/vnd.hp-PCL") && !testfile)
386 testfile = "testfile.pcl";
387 }
388 free(pdl);
389
390 if (testfile)
391 {
392 fprintf(stderr,
393 "Using \"%s\" for printer \"%s\" based on TXT record pdl "
394 "info.\n", testfile, device->name);
395 }
396 else
397 {
398 fprintf(stderr,
399 "Printer \"%s\" reports the following TXT file formats:\n",
400 device->name);
401 fprintf(stderr, " \"%s\"\n", device->pdl);
402 }
403 }
404
405 if (!device->ty &&
406 (attr = ippFindAttribute(response, "printer-make-and-model",
407 IPP_TAG_TEXT)) != NULL)
408 device->ty = strdup(attr->values[0].string.text);
409
410 if (strcmp(version, "1.0") && testfile && device->ty)
411 {
412 char filename[1024], /* Filename */
413 *fileptr; /* Pointer into filename */
414 const char *typtr; /* Pointer into ty */
415
416 if (!strncasecmp(device->ty, "DeskJet", 7) ||
417 !strncasecmp(device->ty, "DesignJet", 9) ||
418 !strncasecmp(device->ty, "OfficeJet", 9) ||
419 !strncasecmp(device->ty, "Photosmart", 10))
420 strlcpy(filename, "HP_", sizeof(filename));
421 else
422 filename[0] = '\0';
423
424 fileptr = filename + strlen(filename);
425
426 if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29))
427 typtr = device->ty + 22;
428 else
429 typtr = device->ty;
430
431 while (*typtr && fileptr < (filename + sizeof(filename) - 1))
432 {
433 if (isalnum(*typtr & 255) || *typtr == '-')
434 *fileptr++ = *typtr++;
435 else
436 {
437 *fileptr++ = '_';
438 typtr++;
439 }
440 }
441
442 *fileptr = '\0';
443
444 printf("# %s\n", device->name);
445 printf("echo \"Testing %s...\"\n", device->name);
446
447 if (!ipponly)
448 {
449 printf("echo \"snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 "
450 "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1\" > results/%s.snmpwalk\n",
451 device->host, filename);
452 printf("snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 "
453 "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1 | "
454 "tee -a results/%s.snmpwalk\n",
455 device->host, filename);
456 }
457
458 if (!snmponly)
459 {
460 printf("echo \"./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s "
461 "ipp-%s.test\" > results/%s.log\n", testfile, version,
462 device->uri, version, filename);
463 printf("CUPS_DEBUG_LOG=results/%s.debug_log "
464 "./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s "
465 "ipp-%s.test | tee -a results/%s.log\n", filename,
466 testfile, version, device->uri,
467 version, filename);
468 }
469
470 puts("");
471 }
472 else if (!device->ty)
473 fprintf(stderr,
474 "Ignoring \"%s\" since it doesn't provide a make and model.\n",
475 device->name);
476 else if (!testfile)
477 fprintf(stderr,
478 "Ignoring \"%s\" since it does not support a common format.\n",
479 device->name);
480 else
481 fprintf(stderr, "Ignoring \"%s\" since it only supports IPP/1.0.\n",
482 device->name);
483 }
484
485 ippDelete(response);
486 httpClose(http);
487 }
488
489 return (0);
490}
491
492
493/*
494 * 'browse_callback()' - Browse devices.
495 */
496
497static void
498browse_callback(
499 DNSServiceRef sdRef, /* I - Service reference */
500 DNSServiceFlags flags, /* I - Option flags */
501 uint32_t interfaceIndex, /* I - Interface number */
502 DNSServiceErrorType errorCode, /* I - Error, if any */
503 const char *serviceName, /* I - Name of service/device */
504 const char *regtype, /* I - Type of service */
505 const char *replyDomain, /* I - Service domain */
506 void *context) /* I - Devices array */
507{
508#ifdef DEBUG
509 fprintf(stderr, "browse_callback(sdRef=%p, flags=%x, "
510 "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", "
511 "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n",
512 sdRef, flags, interfaceIndex, errorCode,
513 serviceName ? serviceName : "(null)",
514 regtype ? regtype : "(null)",
515 replyDomain ? replyDomain : "(null)",
516 context);
517#endif /* DEBUG */
518
519 /*
520 * Only process "add" data...
521 */
522
523 if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd))
524 return;
525
526 /*
527 * Get the device...
528 */
529
530 get_device((cups_array_t *)context, serviceName, regtype, replyDomain);
531}
532
533
534/*
535 * 'compare_devices()' - Compare two devices.
536 */
537
538static int /* O - Result of comparison */
539compare_devices(cups_device_t *a, /* I - First device */
540 cups_device_t *b) /* I - Second device */
541{
542 int retval = strcmp(a->name, b->name);
543
544 if (retval)
545 return (retval);
546 else
547 return (-strcmp(a->regtype, b->regtype));
548}
549
550
551/*
552 * 'get_device()' - Create or update a device.
553 */
554
555static cups_device_t * /* O - Device */
556get_device(cups_array_t *devices, /* I - Device array */
557 const char *serviceName, /* I - Name of service/device */
558 const char *regtype, /* I - Type of service */
559 const char *replyDomain) /* I - Service domain */
560{
561 cups_device_t key, /* Search key */
562 *device; /* Device */
563 char fullName[kDNSServiceMaxDomainName];
564 /* Full name for query */
565
566
567 /*
568 * See if this is a new device...
569 */
570
571 key.name = (char *)serviceName;
572 key.regtype = (char *)regtype;
573
574 for (device = cupsArrayFind(devices, &key);
575 device;
576 device = cupsArrayNext(devices))
577 if (strcasecmp(device->name, key.name))
578 break;
579 else
580 {
581 if (!strcasecmp(device->domain, "local.") &&
582 strcasecmp(device->domain, replyDomain))
583 {
584 /*
585 * Update the .local listing to use the "global" domain name instead.
586 * The backend will try local lookups first, then the global domain name.
587 */
588
589 free(device->domain);
590 device->domain = strdup(replyDomain);
591
592 DNSServiceConstructFullName(fullName, device->name, regtype,
593 replyDomain);
594 free(device->fullName);
595 device->fullName = strdup(fullName);
596 }
597
598 return (device);
599 }
600
601 /*
602 * Yes, add the device...
603 */
604
605 device = calloc(sizeof(cups_device_t), 1);
606 device->name = strdup(serviceName);
607 device->domain = strdup(replyDomain);
608 device->regtype = strdup(regtype);
609
610 cupsArrayAdd(devices, device);
611
612 /*
613 * Set the "full name" of this service, which is used for queries...
614 */
615
616 DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain);
617 device->fullName = strdup(fullName);
618
619#ifdef DEBUG
620 fprintf(stderr, "get_device: fullName=\"%s\"...\n", fullName);
621#endif /* DEBUG */
622
623 return (device);
624}
625
626
627/*
628 * 'progress()' - Show query progress.
629 */
630
631static void
632progress(void)
633{
634#ifndef DEBUG
635 const char *chars = "|/-\\";
636 static int count = 0;
637
638
639 fprintf(stderr, "\rLooking for printers %c", chars[count]);
640 fflush(stderr);
641 count = (count + 1) & 3;
642#endif /* !DEBUG */
643}
644
645
646/*
647 * 'resolve_callback()' - Process resolve data.
648 */
649
650static void
651resolve_callback(
652 DNSServiceRef sdRef, /* I - Service reference */
653 DNSServiceFlags flags, /* I - Data flags */
654 uint32_t interfaceIndex, /* I - Interface */
655 DNSServiceErrorType errorCode, /* I - Error, if any */
656 const char *fullName, /* I - Full service name */
657 const char *hostTarget, /* I - Hostname */
658 uint16_t port, /* I - Port number (network byte order) */
659 uint16_t txtLen, /* I - Length of TXT record data */
660 const unsigned char *txtRecord, /* I - TXT record data */
661 void *context) /* I - Device */
662{
663 char temp[257], /* TXT key value */
664 uri[1024]; /* Printer URI */
665 const void *value; /* Value from TXT record */
666 uint8_t valueLen; /* Length of value */
667 cups_device_t *device = (cups_device_t *)context;
668 /* Device */
669
670
671#ifdef DEBUG
672 fprintf(stderr, "\rresolve_callback(sdRef=%p, flags=%x, "
673 "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", "
674 "hostTarget=\"%s\", port=%d, txtLen=%u, txtRecord=%p, "
675 "context=%p)\n",
676 sdRef, flags, interfaceIndex, errorCode,
677 fullName ? fullName : "(null)", hostTarget ? hostTarget : "(null)",
678 ntohs(port), txtLen, txtRecord, context);
679#endif /* DEBUG */
680
681 /*
682 * Only process "add" data...
683 */
684
685 if (errorCode != kDNSServiceErr_NoError)
686 return;
687
688 device->got_resolve = 1;
689 device->host = strdup(hostTarget);
690 device->port = ntohs(port);
691
692 /*
693 * Extract the "remote printer" key from the TXT record and save the URI...
694 */
695
696 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "rp",
697 &valueLen)) != NULL)
698 {
699 if (((char *)value)[0] == '/')
700 {
701 /*
702 * "rp" value (incorrectly) has a leading slash already...
703 */
704
705 memcpy(temp, value, valueLen);
706 temp[valueLen] = '\0';
707 }
708 else
709 {
710 /*
711 * Convert to resource by concatenating with a leading "/"...
712 */
713
714 temp[0] = '/';
715 memcpy(temp + 1, value, valueLen);
716 temp[valueLen + 1] = '\0';
717 }
718 }
719 else
720 {
721 /*
722 * Default "rp" value is blank, mapping to a path of "/"...
723 */
724
725 temp[0] = '/';
726 temp[1] = '\0';
727 }
728
729 if (!strncmp(temp, "/printers/", 10))
730 device->cups_shared = -1;
731
732 httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp",
733 NULL, hostTarget, ntohs(port), temp);
734 device->uri = strdup(uri);
735 device->rp = strdup(temp);
736
737 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "ty",
738 &valueLen)) != NULL)
739 {
740 memcpy(temp, value, valueLen);
741 temp[valueLen] = '\0';
742
743 device->ty = strdup(temp);
744 }
745
746 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "pdl",
747 &valueLen)) != NULL)
748 {
749 memcpy(temp, value, valueLen);
750 temp[valueLen] = '\0';
751
752 device->pdl = strdup(temp);
753 }
754
755 if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "printer-type",
756 &valueLen)) != NULL)
757 device->cups_shared = 1;
758
759 if (device->cups_shared)
760 fprintf(stderr, "\rIgnoring CUPS printer %s\n", uri);
761 else
762 fprintf(stderr, "\rFound IPP printer %s\n", uri);
763
764 progress();
765}
766
767
768/*
769 * 'unquote()' - Unquote a name string.
770 */
771
772static void
773unquote(char *dst, /* I - Destination buffer */
774 const char *src, /* I - Source string */
775 size_t dstsize) /* I - Size of destination buffer */
776{
777 char *dstend = dst + dstsize - 1; /* End of destination buffer */
778
779
780 while (*src && dst < dstend)
781 {
782 if (*src == '\\')
783 {
784 src ++;
785 if (isdigit(src[0] & 255) && isdigit(src[1] & 255) &&
786 isdigit(src[2] & 255))
787 {
788 *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0';
789 src += 3;
790 }
791 else
792 *dst++ = *src++;
793 }
794 else
795 *dst++ = *src ++;
796 }
797
798 *dst = '\0';
799}
800
801
802/*
803 * 'usage()' - Show program usage and exit.
804 */
805
806static void
807usage(void)
808{
809 _cupsLangPuts(stdout, _("Usage: ippdiscover [options] -a\n"
810 " ippdiscover [options] \"service name\"\n"
811 "\n"
812 "Options:"));
813 _cupsLangPuts(stdout, _(" -a Browse for all services."));
814 _cupsLangPuts(stdout, _(" -d domain Browse/resolve in specified domain."));
815 _cupsLangPuts(stdout, _(" -p program Run specified program for each service."));
816 _cupsLangPuts(stdout, _(" -t type Browse/resolve with specified type."));
817
818 exit(0);
819}