| /* |
| * "$Id$" |
| * |
| * ippdiscover command for CUPS. |
| * |
| * Copyright 2007-2013 by Apple Inc. |
| * Copyright 1997-2007 by Easy Software Products. |
| * |
| * These coded instructions, statements, and computer programs are the |
| * property of Apple Inc. and are protected by Federal copyright |
| * law. Distribution and use rights are outlined in the file "LICENSE.txt" |
| * which should have been included with this file. If this file is |
| * file is missing or damaged, see the license at "http://www.cups.org/". |
| * |
| * This file is subject to the Apple OS-Developed Software exception. |
| * |
| * Contents: |
| * |
| */ |
| |
| |
| /* |
| * Include necessary headers. |
| */ |
| |
| #include <cups/cups-private.h> |
| #ifdef HAVE_DNSSD |
| # include <dns_sd.h> |
| # ifdef WIN32 |
| # pragma comment(lib, "dnssd.lib") |
| # endif /* WIN32 */ |
| #endif /* HAVE_DNSSD */ |
| #ifdef HAVE_AVAHI |
| # include <avahi-client/client.h> |
| # include <avahi-client/lookup.h> |
| # include <avahi-common/simple-watch.h> |
| # include <avahi-common/domain.h> |
| # include <avahi-common/error.h> |
| # include <avahi-common/malloc.h> |
| #define kDNSServiceMaxDomainName AVAHI_DOMAIN_NAME_MAX |
| #endif /* HAVE_AVAHI */ |
| |
| |
| /* |
| * Local globals... |
| */ |
| |
| #ifdef HAVE_AVAHI |
| static int got_data = 0; /* Got data from poll? */ |
| static AvahiSimplePoll *simple_poll = NULL; |
| /* Poll information */ |
| #endif /* HAVE_AVAHI */ |
| static const char *program = NULL;/* Program to run */ |
| |
| |
| /* |
| * Local functions... |
| */ |
| |
| #ifdef HAVE_DNSSD |
| static void DNSSD_API browse_callback(DNSServiceRef sdRef, |
| DNSServiceFlags flags, |
| uint32_t interfaceIndex, |
| DNSServiceErrorType errorCode, |
| const char *serviceName, |
| const char *regtype, |
| const char *replyDomain, void *context) |
| __attribute__((nonnull(1,5,6,7,8))); |
| static void DNSSD_API resolve_cb(DNSServiceRef sdRef, |
| DNSServiceFlags flags, |
| uint32_t interfaceIndex, |
| DNSServiceErrorType errorCode, |
| const char *fullName, |
| const char *hostTarget, |
| uint16_t port, uint16_t txtLen, |
| const unsigned char *txtRecord, |
| void *context); |
| #endif /* HAVE_DNSSD */ |
| |
| #ifdef HAVE_AVAHI |
| static void browse_callback(AvahiServiceBrowser *browser, |
| AvahiIfIndex interface, |
| AvahiProtocol protocol, |
| AvahiBrowserEvent event, |
| const char *serviceName, |
| const char *regtype, |
| const char *replyDomain, |
| AvahiLookupResultFlags flags, |
| void *context); |
| static void client_cb(AvahiClient *client, AvahiClientState state, |
| void *simple_poll); |
| static int poll_cb(struct pollfd *pollfds, unsigned int num_pollfds, |
| int timeout, void *context); |
| static void resolve_cb(AvahiServiceResolver *resolver, |
| AvahiIfIndex interface, |
| AvahiProtocol protocol, |
| AvahiResolverEvent event, |
| const char *name, const char *type, |
| const char *domain, const char *host_name, |
| const AvahiAddress *address, uint16_t port, |
| AvahiStringList *txt, |
| AvahiLookupResultFlags flags, void *context); |
| #endif /* HAVE_AVAHI */ |
| |
| static void resolve_and_run(const char *name, const char *type, |
| const char *domain); |
| static void unquote(char *dst, const char *src, size_t dstsize); |
| static void usage(void) __attribute__((noreturn)); |
| |
| |
| /* |
| * 'main()' - Browse for printers and run the specified command. |
| */ |
| |
| int /* O - Exit status */ |
| main(int argc, /* I - Number of command-line args */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i; /* Looping var */ |
| const char *opt, /* Current option character */ |
| *name = NULL, /* Service name */ |
| *type = "_ipp._tcp", /* Service type */ |
| *domain = "local."; /* Service domain */ |
| #ifdef HAVE_DNSSD |
| DNSServiceRef ref; /* Browsing service reference */ |
| #endif /* HAVE_DNSSD */ |
| #ifdef HAVE_AVAHI |
| AvahiClient *client; /* Client information */ |
| int error; /* Error code, if any */ |
| #endif /* HAVE_AVAHI */ |
| |
| |
| for (i = 1; i < argc; i ++) |
| if (!strcmp(argv[i], "snmp")) |
| snmponly = 1; |
| else if (!strcmp(argv[i], "ipp")) |
| ipponly = 1; |
| else |
| { |
| puts("Usage: ./ipp-printers [{ipp | snmp}]"); |
| return (1); |
| } |
| |
| /* |
| * Create an array to track devices... |
| */ |
| |
| devices = cupsArrayNew((cups_array_func_t)compare_devices, NULL); |
| |
| /* |
| * Browse for different kinds of printers... |
| */ |
| |
| if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError) |
| { |
| perror("ERROR: Unable to create service connection"); |
| return (1); |
| } |
| |
| fd = DNSServiceRefSockFD(main_ref); |
| |
| ipp_ref = main_ref; |
| DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, |
| "_ipp._tcp", NULL, browse_callback, devices); |
| |
| /* |
| * Loop until we are killed... |
| */ |
| |
| progress(); |
| |
| for (;;) |
| { |
| FD_ZERO(&input); |
| FD_SET(fd, &input); |
| |
| timeout.tv_sec = 2; |
| timeout.tv_usec = 500000; |
| |
| if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0) |
| { |
| time_t curtime = time(NULL); |
| |
| for (device = (cups_device_t *)cupsArrayFirst(devices); |
| device; |
| device = (cups_device_t *)cupsArrayNext(devices)) |
| if (!device->got_resolve) |
| { |
| if (!device->ref) |
| break; |
| |
| if ((curtime - device->resolve_time) > 10) |
| { |
| device->got_resolve = -1; |
| fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n", |
| device->name); |
| progress(); |
| } |
| else |
| break; |
| } |
| |
| if (!device) |
| break; |
| } |
| |
| if (FD_ISSET(fd, &input)) |
| { |
| /* |
| * Process results of our browsing... |
| */ |
| |
| progress(); |
| DNSServiceProcessResult(main_ref); |
| } |
| else |
| { |
| /* |
| * Query any devices we've found... |
| */ |
| |
| DNSServiceErrorType status; /* DNS query status */ |
| int count; /* Number of queries */ |
| |
| |
| for (device = (cups_device_t *)cupsArrayFirst(devices), count = 0; |
| device; |
| device = (cups_device_t *)cupsArrayNext(devices)) |
| { |
| if (!device->ref && !device->sent) |
| { |
| /* |
| * Found the device, now get the TXT record(s) for it... |
| */ |
| |
| if (count < 50) |
| { |
| device->resolve_time = time(NULL); |
| device->ref = main_ref; |
| |
| status = DNSServiceResolve(&(device->ref), |
| kDNSServiceFlagsShareConnection, |
| 0, device->name, device->regtype, |
| device->domain, resolve_callback, |
| device); |
| if (status != kDNSServiceErr_NoError) |
| { |
| fprintf(stderr, "\rUnable to resolve \"%s\": %d\n", |
| device->name, status); |
| progress(); |
| } |
| else |
| count ++; |
| } |
| } |
| else if (!device->sent && device->got_resolve) |
| { |
| /* |
| * Got the TXT records, now report the device... |
| */ |
| |
| DNSServiceRefDeallocate(device->ref); |
| device->ref = 0; |
| device->sent = 1; |
| } |
| } |
| } |
| } |
| |
| #ifndef DEBUG |
| fprintf(stderr, "\rFound %d printers. Now querying for capabilities...\n", |
| cupsArrayCount(devices)); |
| #endif /* !DEBUG */ |
| |
| puts("#!/bin/sh -x"); |
| puts("test -d results && rm -rf results"); |
| puts("mkdir results"); |
| puts("CUPS_DEBUG_LEVEL=6; export CUPS_DEBUG_LEVEL"); |
| puts("CUPS_DEBUG_FILTER='^(ipp|http|_ipp|_http|cupsGetResponse|cupsSend|" |
| "cupsWrite|cupsDo).*'; export CUPS_DEBUG_FILTER"); |
| |
| for (device = (cups_device_t *)cupsArrayFirst(devices); |
| device; |
| device = (cups_device_t *)cupsArrayNext(devices)) |
| { |
| if (device->got_resolve <= 0 || device->cups_shared) |
| continue; |
| |
| #ifdef DEBUG |
| fprintf(stderr, "Checking \"%s\" (got_resolve=%d, cups_shared=%d, uri=%s)\n", |
| device->name, device->got_resolve, device->cups_shared, device->uri); |
| #else |
| fprintf(stderr, "Checking \"%s\"...\n", device->name); |
| #endif /* DEBUG */ |
| |
| if ((http = httpConnect(device->host, device->port)) == NULL) |
| { |
| fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name, |
| cupsLastErrorString()); |
| continue; |
| } |
| |
| request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); |
| ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, |
| device->uri); |
| |
| response = cupsDoRequest(http, request, device->rp); |
| |
| if (cupsLastError() > IPP_OK_SUBST) |
| fprintf(stderr, "Failed to query \"%s\": %s\n", device->name, |
| cupsLastErrorString()); |
| else |
| { |
| if ((attr = ippFindAttribute(response, "ipp-versions-supported", |
| IPP_TAG_KEYWORD)) != NULL) |
| { |
| version = attr->values[0].string.text; |
| |
| for (i = 1; i < attr->num_values; i ++) |
| if (strcmp(attr->values[i].string.text, version) > 0) |
| version = attr->values[i].string.text; |
| } |
| else |
| version = "1.0"; |
| |
| testfile = NULL; |
| |
| if ((attr = ippFindAttribute(response, "document-format-supported", |
| IPP_TAG_MIMETYPE)) != NULL) |
| { |
| /* |
| * Figure out the test file for printing, preferring PDF and PostScript |
| * over JPEG and plain text... |
| */ |
| |
| for (i = 0; i < attr->num_values; i ++) |
| { |
| if (!strcasecmp(attr->values[i].string.text, "application/pdf")) |
| { |
| testfile = "testfile.pdf"; |
| break; |
| } |
| else if (!strcasecmp(attr->values[i].string.text, |
| "application/postscript")) |
| testfile = "testfile.ps"; |
| else if (!strcasecmp(attr->values[i].string.text, "image/jpeg") && |
| !testfile) |
| testfile = "testfile.jpg"; |
| else if (!strcasecmp(attr->values[i].string.text, "text/plain") && |
| !testfile) |
| testfile = "testfile.txt"; |
| else if (!strcasecmp(attr->values[i].string.text, |
| "application/vnd.hp-PCL") && !testfile) |
| testfile = "testfile.pcl"; |
| } |
| |
| if (!testfile) |
| { |
| fprintf(stderr, |
| "Printer \"%s\" reports the following IPP file formats:\n", |
| device->name); |
| for (i = 0; i < attr->num_values; i ++) |
| fprintf(stderr, " \"%s\"\n", attr->values[i].string.text); |
| } |
| } |
| |
| if (!testfile && device->pdl) |
| { |
| char *pdl, /* Copy of pdl string */ |
| *start, *end; /* Pointers into pdl string */ |
| |
| |
| pdl = strdup(device->pdl); |
| for (start = device->pdl; start && *start; start = end) |
| { |
| if ((end = strchr(start, ',')) != NULL) |
| *end++ = '\0'; |
| |
| if (!strcasecmp(start, "application/pdf")) |
| { |
| testfile = "testfile.pdf"; |
| break; |
| } |
| else if (!strcasecmp(start, "application/postscript")) |
| testfile = "testfile.ps"; |
| else if (!strcasecmp(start, "image/jpeg") && !testfile) |
| testfile = "testfile.jpg"; |
| else if (!strcasecmp(start, "text/plain") && !testfile) |
| testfile = "testfile.txt"; |
| else if (!strcasecmp(start, "application/vnd.hp-PCL") && !testfile) |
| testfile = "testfile.pcl"; |
| } |
| free(pdl); |
| |
| if (testfile) |
| { |
| fprintf(stderr, |
| "Using \"%s\" for printer \"%s\" based on TXT record pdl " |
| "info.\n", testfile, device->name); |
| } |
| else |
| { |
| fprintf(stderr, |
| "Printer \"%s\" reports the following TXT file formats:\n", |
| device->name); |
| fprintf(stderr, " \"%s\"\n", device->pdl); |
| } |
| } |
| |
| if (!device->ty && |
| (attr = ippFindAttribute(response, "printer-make-and-model", |
| IPP_TAG_TEXT)) != NULL) |
| device->ty = strdup(attr->values[0].string.text); |
| |
| if (strcmp(version, "1.0") && testfile && device->ty) |
| { |
| char filename[1024], /* Filename */ |
| *fileptr; /* Pointer into filename */ |
| const char *typtr; /* Pointer into ty */ |
| |
| if (!strncasecmp(device->ty, "DeskJet", 7) || |
| !strncasecmp(device->ty, "DesignJet", 9) || |
| !strncasecmp(device->ty, "OfficeJet", 9) || |
| !strncasecmp(device->ty, "Photosmart", 10)) |
| strlcpy(filename, "HP_", sizeof(filename)); |
| else |
| filename[0] = '\0'; |
| |
| fileptr = filename + strlen(filename); |
| |
| if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29)) |
| typtr = device->ty + 22; |
| else |
| typtr = device->ty; |
| |
| while (*typtr && fileptr < (filename + sizeof(filename) - 1)) |
| { |
| if (isalnum(*typtr & 255) || *typtr == '-') |
| *fileptr++ = *typtr++; |
| else |
| { |
| *fileptr++ = '_'; |
| typtr++; |
| } |
| } |
| |
| *fileptr = '\0'; |
| |
| printf("# %s\n", device->name); |
| printf("echo \"Testing %s...\"\n", device->name); |
| |
| if (!ipponly) |
| { |
| printf("echo \"snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " |
| "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1\" > results/%s.snmpwalk\n", |
| device->host, filename); |
| printf("snmpwalk -c public -v 1 -Cc %s 1.3.6.1.2.1.25 " |
| "1.3.6.1.2.1.43 1.3.6.1.4.1.2699.1 | " |
| "tee -a results/%s.snmpwalk\n", |
| device->host, filename); |
| } |
| |
| if (!snmponly) |
| { |
| printf("echo \"./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " |
| "ipp-%s.test\" > results/%s.log\n", testfile, version, |
| device->uri, version, filename); |
| printf("CUPS_DEBUG_LOG=results/%s.debug_log " |
| "./ipptool-static -tIf %s -T 30 -d NOPRINT=1 -V %s %s " |
| "ipp-%s.test | tee -a results/%s.log\n", filename, |
| testfile, version, device->uri, |
| version, filename); |
| } |
| |
| puts(""); |
| } |
| else if (!device->ty) |
| fprintf(stderr, |
| "Ignoring \"%s\" since it doesn't provide a make and model.\n", |
| device->name); |
| else if (!testfile) |
| fprintf(stderr, |
| "Ignoring \"%s\" since it does not support a common format.\n", |
| device->name); |
| else |
| fprintf(stderr, "Ignoring \"%s\" since it only supports IPP/1.0.\n", |
| device->name); |
| } |
| |
| ippDelete(response); |
| httpClose(http); |
| } |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'browse_callback()' - Browse devices. |
| */ |
| |
| static void |
| browse_callback( |
| DNSServiceRef sdRef, /* I - Service reference */ |
| DNSServiceFlags flags, /* I - Option flags */ |
| uint32_t interfaceIndex, /* I - Interface number */ |
| DNSServiceErrorType errorCode, /* I - Error, if any */ |
| const char *serviceName, /* I - Name of service/device */ |
| const char *regtype, /* I - Type of service */ |
| const char *replyDomain, /* I - Service domain */ |
| void *context) /* I - Devices array */ |
| { |
| #ifdef DEBUG |
| fprintf(stderr, "browse_callback(sdRef=%p, flags=%x, " |
| "interfaceIndex=%d, errorCode=%d, serviceName=\"%s\", " |
| "regtype=\"%s\", replyDomain=\"%s\", context=%p)\n", |
| sdRef, flags, interfaceIndex, errorCode, |
| serviceName ? serviceName : "(null)", |
| regtype ? regtype : "(null)", |
| replyDomain ? replyDomain : "(null)", |
| context); |
| #endif /* DEBUG */ |
| |
| /* |
| * Only process "add" data... |
| */ |
| |
| if (errorCode != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) |
| return; |
| |
| /* |
| * Get the device... |
| */ |
| |
| get_device((cups_array_t *)context, serviceName, regtype, replyDomain); |
| } |
| |
| |
| /* |
| * 'compare_devices()' - Compare two devices. |
| */ |
| |
| static int /* O - Result of comparison */ |
| compare_devices(cups_device_t *a, /* I - First device */ |
| cups_device_t *b) /* I - Second device */ |
| { |
| int retval = strcmp(a->name, b->name); |
| |
| if (retval) |
| return (retval); |
| else |
| return (-strcmp(a->regtype, b->regtype)); |
| } |
| |
| |
| /* |
| * 'get_device()' - Create or update a device. |
| */ |
| |
| static cups_device_t * /* O - Device */ |
| get_device(cups_array_t *devices, /* I - Device array */ |
| const char *serviceName, /* I - Name of service/device */ |
| const char *regtype, /* I - Type of service */ |
| const char *replyDomain) /* I - Service domain */ |
| { |
| cups_device_t key, /* Search key */ |
| *device; /* Device */ |
| char fullName[kDNSServiceMaxDomainName]; |
| /* Full name for query */ |
| |
| |
| /* |
| * See if this is a new device... |
| */ |
| |
| key.name = (char *)serviceName; |
| key.regtype = (char *)regtype; |
| |
| for (device = cupsArrayFind(devices, &key); |
| device; |
| device = cupsArrayNext(devices)) |
| if (strcasecmp(device->name, key.name)) |
| break; |
| else |
| { |
| if (!strcasecmp(device->domain, "local.") && |
| strcasecmp(device->domain, replyDomain)) |
| { |
| /* |
| * Update the .local listing to use the "global" domain name instead. |
| * The backend will try local lookups first, then the global domain name. |
| */ |
| |
| free(device->domain); |
| device->domain = strdup(replyDomain); |
| |
| DNSServiceConstructFullName(fullName, device->name, regtype, |
| replyDomain); |
| free(device->fullName); |
| device->fullName = strdup(fullName); |
| } |
| |
| return (device); |
| } |
| |
| /* |
| * Yes, add the device... |
| */ |
| |
| device = calloc(sizeof(cups_device_t), 1); |
| device->name = strdup(serviceName); |
| device->domain = strdup(replyDomain); |
| device->regtype = strdup(regtype); |
| |
| cupsArrayAdd(devices, device); |
| |
| /* |
| * Set the "full name" of this service, which is used for queries... |
| */ |
| |
| DNSServiceConstructFullName(fullName, serviceName, regtype, replyDomain); |
| device->fullName = strdup(fullName); |
| |
| #ifdef DEBUG |
| fprintf(stderr, "get_device: fullName=\"%s\"...\n", fullName); |
| #endif /* DEBUG */ |
| |
| return (device); |
| } |
| |
| |
| /* |
| * 'progress()' - Show query progress. |
| */ |
| |
| static void |
| progress(void) |
| { |
| #ifndef DEBUG |
| const char *chars = "|/-\\"; |
| static int count = 0; |
| |
| |
| fprintf(stderr, "\rLooking for printers %c", chars[count]); |
| fflush(stderr); |
| count = (count + 1) & 3; |
| #endif /* !DEBUG */ |
| } |
| |
| |
| /* |
| * 'resolve_callback()' - Process resolve data. |
| */ |
| |
| static void |
| resolve_callback( |
| DNSServiceRef sdRef, /* I - Service reference */ |
| DNSServiceFlags flags, /* I - Data flags */ |
| uint32_t interfaceIndex, /* I - Interface */ |
| DNSServiceErrorType errorCode, /* I - Error, if any */ |
| const char *fullName, /* I - Full service name */ |
| const char *hostTarget, /* I - Hostname */ |
| uint16_t port, /* I - Port number (network byte order) */ |
| uint16_t txtLen, /* I - Length of TXT record data */ |
| const unsigned char *txtRecord, /* I - TXT record data */ |
| void *context) /* I - Device */ |
| { |
| char temp[257], /* TXT key value */ |
| uri[1024]; /* Printer URI */ |
| const void *value; /* Value from TXT record */ |
| uint8_t valueLen; /* Length of value */ |
| cups_device_t *device = (cups_device_t *)context; |
| /* Device */ |
| |
| |
| #ifdef DEBUG |
| fprintf(stderr, "\rresolve_callback(sdRef=%p, flags=%x, " |
| "interfaceIndex=%d, errorCode=%d, fullName=\"%s\", " |
| "hostTarget=\"%s\", port=%d, txtLen=%u, txtRecord=%p, " |
| "context=%p)\n", |
| sdRef, flags, interfaceIndex, errorCode, |
| fullName ? fullName : "(null)", hostTarget ? hostTarget : "(null)", |
| ntohs(port), txtLen, txtRecord, context); |
| #endif /* DEBUG */ |
| |
| /* |
| * Only process "add" data... |
| */ |
| |
| if (errorCode != kDNSServiceErr_NoError) |
| return; |
| |
| device->got_resolve = 1; |
| device->host = strdup(hostTarget); |
| device->port = ntohs(port); |
| |
| /* |
| * Extract the "remote printer" key from the TXT record and save the URI... |
| */ |
| |
| if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "rp", |
| &valueLen)) != NULL) |
| { |
| if (((char *)value)[0] == '/') |
| { |
| /* |
| * "rp" value (incorrectly) has a leading slash already... |
| */ |
| |
| memcpy(temp, value, valueLen); |
| temp[valueLen] = '\0'; |
| } |
| else |
| { |
| /* |
| * Convert to resource by concatenating with a leading "/"... |
| */ |
| |
| temp[0] = '/'; |
| memcpy(temp + 1, value, valueLen); |
| temp[valueLen + 1] = '\0'; |
| } |
| } |
| else |
| { |
| /* |
| * Default "rp" value is blank, mapping to a path of "/"... |
| */ |
| |
| temp[0] = '/'; |
| temp[1] = '\0'; |
| } |
| |
| if (!strncmp(temp, "/printers/", 10)) |
| device->cups_shared = -1; |
| |
| httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", |
| NULL, hostTarget, ntohs(port), temp); |
| device->uri = strdup(uri); |
| device->rp = strdup(temp); |
| |
| if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "ty", |
| &valueLen)) != NULL) |
| { |
| memcpy(temp, value, valueLen); |
| temp[valueLen] = '\0'; |
| |
| device->ty = strdup(temp); |
| } |
| |
| if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "pdl", |
| &valueLen)) != NULL) |
| { |
| memcpy(temp, value, valueLen); |
| temp[valueLen] = '\0'; |
| |
| device->pdl = strdup(temp); |
| } |
| |
| if ((value = TXTRecordGetValuePtr(txtLen, txtRecord, "printer-type", |
| &valueLen)) != NULL) |
| device->cups_shared = 1; |
| |
| if (device->cups_shared) |
| fprintf(stderr, "\rIgnoring CUPS printer %s\n", uri); |
| else |
| fprintf(stderr, "\rFound IPP printer %s\n", uri); |
| |
| progress(); |
| } |
| |
| |
| /* |
| * 'unquote()' - Unquote a name string. |
| */ |
| |
| static void |
| unquote(char *dst, /* I - Destination buffer */ |
| const char *src, /* I - Source string */ |
| size_t dstsize) /* I - Size of destination buffer */ |
| { |
| char *dstend = dst + dstsize - 1; /* End of destination buffer */ |
| |
| |
| while (*src && dst < dstend) |
| { |
| if (*src == '\\') |
| { |
| src ++; |
| if (isdigit(src[0] & 255) && isdigit(src[1] & 255) && |
| isdigit(src[2] & 255)) |
| { |
| *dst++ = ((((src[0] - '0') * 10) + src[1] - '0') * 10) + src[2] - '0'; |
| src += 3; |
| } |
| else |
| *dst++ = *src++; |
| } |
| else |
| *dst++ = *src ++; |
| } |
| |
| *dst = '\0'; |
| } |
| |
| |
| /* |
| * 'usage()' - Show program usage and exit. |
| */ |
| |
| static void |
| usage(void) |
| { |
| _cupsLangPuts(stdout, _("Usage: ippdiscover [options] -a\n" |
| " ippdiscover [options] \"service name\"\n" |
| "\n" |
| "Options:")); |
| _cupsLangPuts(stdout, _(" -a Browse for all services.")); |
| _cupsLangPuts(stdout, _(" -d domain Browse/resolve in specified domain.")); |
| _cupsLangPuts(stdout, _(" -p program Run specified program for each service.")); |
| _cupsLangPuts(stdout, _(" -t type Browse/resolve with specified type.")); |
| |
| exit(0); |
| } |
| |
| |
| /* |
| * End of "$Id$". |
| */ |