From 766a82295781e5923fda8329c981959b7b77558b Mon Sep 17 00:00:00 2001 From: msweet Date: Wed, 5 Jun 2013 16:05:45 +0000 Subject: [PATCH] Save work on ippfind program. Nearly there. git-svn-id: svn+ssh://src.apple.com/svn/cups/cups.org/trunk@11010 a1ca3aef-8c08-0410-bb20-df032aa958be --- Makedefs.in | 2 + config-scripts/cups-dnssd.m4 | 10 + doc/help/man-ippfind.html | 294 +++++++ man/Makefile | 3 +- man/ippfind.man | 229 ++++++ test/Dependencies | 31 +- test/Makefile | 14 + test/ippfind.c | 1474 +++++++++++++++++++++++++--------- test/run-stp-tests.sh | 7 +- 9 files changed, 1659 insertions(+), 405 deletions(-) create mode 100644 doc/help/man-ippfind.html create mode 100644 man/ippfind.man diff --git a/Makedefs.in b/Makedefs.in index c971b0200..676e0dd64 100644 --- a/Makedefs.in +++ b/Makedefs.in @@ -138,6 +138,8 @@ DNSSD_BACKEND = @DNSSD_BACKEND@ DSOFLAGS = -L../cups @DSOFLAGS@ DSOLIBS = @DSOLIBS@ $(COMMONLIBS) DNSSDLIBS = @DNSSDLIBS@ +IPPFIND_BIN = @IPPFIND_BIN@ +IPPFIND_MAN = @IPPFIND_MAN@ LAUNCHDLIBS = @LAUNCHDLIBS@ LDFLAGS = -L../cgi-bin -L../cups -L../filter -L../ppdc \ -L../scheduler @LDARCHFLAGS@ \ diff --git a/config-scripts/cups-dnssd.m4 b/config-scripts/cups-dnssd.m4 index 426637a20..02974ef8a 100644 --- a/config-scripts/cups-dnssd.m4 +++ b/config-scripts/cups-dnssd.m4 @@ -23,6 +23,8 @@ AC_ARG_WITH(dnssd-includes, [ --with-dnssd-includes set directory for DNS Ser DNSSDLIBS="" DNSSD_BACKEND="" +IPPFIND_BIN="" +IPPFIND_MAN="" if test "x$PKGCONFIG" != x -a x$enable_avahi != xno; then AC_MSG_CHECKING(for Avahi) @@ -31,6 +33,8 @@ if test "x$PKGCONFIG" != x -a x$enable_avahi != xno; then CFLAGS="$CFLAGS `$PKGCONFIG --cflags avahi-client`" DNSSDLIBS="`$PKGCONFIG --libs avahi-client`" DNSSD_BACKEND="dnssd" + IPPFIND_BIN="ippfind" + IPPFIND_MAN="ippfind.\$(MAN1EXT)" AC_DEFINE(HAVE_AVAHI) else AC_MSG_RESULT(no) @@ -45,6 +49,8 @@ if test "x$DNSSD_BACKEND" = x -a x$enable_dnssd != xno; then AC_DEFINE(HAVE_DNSSD) DNSSDLIBS="-framework CoreFoundation -framework SystemConfiguration" DNSSD_BACKEND="dnssd" + IPPFIND_BIN="ippfind" + IPPFIND_MAN="ippfind.\$(MAN1EXT)" ;; *) # All others... @@ -61,6 +67,8 @@ if test "x$DNSSD_BACKEND" = x -a x$enable_dnssd != xno; then AC_DEFINE(HAVE_DNSSD) DNSSDLIBS="-ldns_sd" DNSSD_BACKEND="dnssd", + IPPFIND_BIN="ippfind" + IPPFIND_MAN="ippfind.\$(MAN1EXT)" AC_MSG_RESULT(no)) LIBS="$SAVELIBS" ;; @@ -70,6 +78,8 @@ fi AC_SUBST(DNSSDLIBS) AC_SUBST(DNSSD_BACKEND) +AC_SUBST(IPPFIND_BIN) +AC_SUBST(IPPFIND_MAN) dnl dnl End of "$Id: cups-dnssd.m4 7890 2008-08-29 22:19:39Z mike $". diff --git a/doc/help/man-ippfind.html b/doc/help/man-ippfind.html new file mode 100644 index 000000000..32c53985d --- /dev/null +++ b/doc/help/man-ippfind.html @@ -0,0 +1,294 @@ + + + + + + ippfind(1) + + +

ippfind(1)

+

Name

+ippfind - find internet printing protocol printers +

Synopsis

+ippfind +[ +options +] regtype[,subtype][.domain.] ... [ +expression ... +] +ippfind +[ +options +] name[.regtype[.domain.]] ... [ +expression ... +] +ippfind +--help +ippfind +--version +

Supported Registration Types

+ippfind supports the following registration types: +
+
_http._tcp +
+
HyperText Transport Protocol (HTTP, RFC 2616) +
+
_https._tcp +
+
Secure HyperText Transport Protocol (HTTPS, RFC 2818) +
+
_ipp._tcp +
+
Internet Printing Protocol (IPP, RFC 2911) +
+
_ipps._tcp +
+
Secure Internet Printing Protocol (IPPS, draft) +
+
_printer._tcp +
+
Line Printer Daemon (LPD, RFC 1179) + +
+
+

Options

+
+
--help +
+
Show program help +
+
--version +
+
Show program version +
+
-4 +
+
Use IPv4 when listing +
+
-6 +
+
Use IPv6 when listing +
+
-T seconds +
+
Specify find timeout in seconds. If 1 or less, ippfind stops as soon as it thinks it has found everything. The default is 1 second. +
+
-V version +
+
Specifies the IPP version when listing. Supported values are 1.1, 2.0, 2.1, and 2.2. + +
+
+

Description

+ippfind finds printer services registered with the local DNS infrastructure or available through the local links. + +

Expressions

+ippfind supports expressions much like the find(1) utility. However, unlike find, ippfind uses POSIX regular expressions instead of shell filename matching patterns. If -e, --exec, -l, --ls, -p, --print, --print-name, -q, --quiet, or -s is not specified, ippfind adds --print to print the service URI of anything it finds. The following expressions are supported: +
+
-d regex +
+
+
--domain regex +
+
True if the domain matches the given regular expression. +
+
-e utility [argument ...] ; +
+
+
--exec utility [argument ...] ; +
+
Executes the specified program if the current result is true. "{foo}" arguments are replaced with the corresponding value - see SUBSTITUTIONS below. +
+
--false +
+
Always false. +
+
-l +
+
+
--ls +
+
Lists attributes returned by Get-Printer-Attributes for IPP printers and traditional find "-ls" output for HTTP URLs. The result is true if the URI is accessible, false otherwise. +
+
--local +
+
True if the service is local to this computer. +
+
-n regex +
+
+
--name regex +
+
True if the service instance name matches the given regular expression. +
+
--path regex +
+
True if the URI resource path matches the given regular expression. +
+
-p +
+
+
--print +
+
Prints the URI if the result of previous expressions is true. The result is always true. +
+
-q +
+
+
--quiet +
+
Quiet mode - just returns the exit codes below. +
+
-r +
+
+
--remote +
+
True if the service is not local to this computer. +
+
-s +
+
+
--print-name +
+
Prints the service instance name if the result of previous expressions is true. The result is always true. +
+
--true +
+
Always true. +
+
-t key +
+
+
--txt key +
+
True if the TXT record contains the named key. +
+
--txt-key regex +
+
True if the TXT record contains the named key and matches the given regular +expression. +
+
-u regex +
+
+
--uri regex +
+
True if the URI matches the given regular expression. +
+
+

Expressions may also contain modifiers: +

+
( expression ) +
+
Group the result of expressions. +
+
! expression +
+
+
--not expression +
+
Unary NOT of the expression. +
+
expression expression +
+
+
expression --and expression +
+
Logical AND of expressions. +
+
expression --or expression +
+
Logical OR of expressions. + +
+
+

Substitutions

+The substitutions for "{foo}" in -e and --exec are: +
+
{} +
+
URI +
+
{service_domain} +
+
Domain name, e.g., "example.com.", "local.", etc. +
+
{service_hostname} +
+
Fully-qualified domain name, e.g., "printer.example.com.", "printer.local.", etc. +
+
{service_name} +
+
Service instance name, e.g., "My Fine Printer". +
+
{service_port} +
+
Port number for server, typically 631 for IPP and 80 for HTTP. +
+
{service_regtype} +
+
DNS-SD registration type, e.g., "_ipp._tcp", "_http._tcp", etc. +
+
{service_scheme} +
+
URI scheme for DNS-SD registration type, e.g., "ipp", "http", etc. +
+
{service_uri} +
+
URI for service, e.g., "ipp://printer.local./ipp/print", "http://printer.local./", etc. +
+
{txt_key} +
+
Value of TXT record key (lowercase). + +
+
+

Environment Variables

+When executing a program, ippfind sets the following environment variables for the matching service registration: +
+
IPPFIND_SERVICE_DOMAIN +
+
Domain name, e.g., "example.com.", "local.", etc. +
+
IPPFIND_SERVICE_HOSTNAME +
+
Fully-qualified domain name, e.g., "printer.example.com.", "printer.local.", etc. +
+
IPPFIND_SERVICE_NAME +
+
Service instance name, e.g., "My Fine Printer". +
+
IPPFIND_SERVICE_PORT +
+
Port number for server, typically 631 for IPP and 80 for HTTP. +
+
IPPFIND_SERVICE_REGTYPE +
+
DNS-SD registration type, e.g., "_ipp._tcp", "_http._tcp", etc. +
+
IPPFIND_SERVICE_SCHEME +
+
URI scheme for DNS-SD registration type, e.g., "ipp", "http", etc. +
+
IPPFIND_SERVICE_URI +
+
URI for service, e.g., "ipp://printer.local./ipp/print", "http://printer.local./", etc. +
+
IPPFIND_TXT_KEY +
+
Values of TXT record KEY (uppercase). + +
+
+

Exit Codes

+ippfind returns 0 if the result for all processed expressions is true, 1 if the result of any processed expression is false, 2 if browsing or any query or resolution failed, 3 if an undefined option or invalid expression was specified, and 4 if it ran out of memory. + +

See Also

+ipptool(1) + +

Copyright

+Copyright 2013 by Apple Inc. + + + diff --git a/man/Makefile b/man/Makefile index 29e0557aa..37c51a478 100644 --- a/man/Makefile +++ b/man/Makefile @@ -3,7 +3,7 @@ # # Man page makefile for CUPS. # -# Copyright 2007-2012 by Apple Inc. +# Copyright 2007-2013 by Apple Inc. # Copyright 1993-2006 by Easy Software Products. # # These coded instructions, statements, and computer programs are the @@ -24,6 +24,7 @@ MAN1 = cancel.$(MAN1EXT) \ cups-config.$(MAN1EXT) \ cupstestdsc.$(MAN1EXT) \ cupstestppd.$(MAN1EXT) \ + $(IPPFIND_MAN) \ ipptool.$(MAN1EXT) \ lp.$(MAN1EXT) \ lpoptions.$(MAN1EXT) \ diff --git a/man/ippfind.man b/man/ippfind.man new file mode 100644 index 000000000..0741a3ea7 --- /dev/null +++ b/man/ippfind.man @@ -0,0 +1,229 @@ +.\" +.\" "$Id$" +.\" +.\" ippfind man page for CUPS. +.\" +.\" Copyright 2013 by Apple Inc. +.\" +.\" 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/". +.\" +.TH ippfind 1 "CUPS" "5 June 2013" "Apple Inc." +.SH NAME +ippfind - find internet printing protocol printers +.SH SYNOPSIS +.B ippfind +[ +.I options +] regtype[,subtype][.domain.] ... [ +.I expression ... +] +.B ippfind +[ +.I options +] name[.regtype[.domain.]] ... [ +.I expression ... +] +.B ippfind +--help +.B ippfind +--version +.SH SUPPORTED REGISTRATION TYPES +\fIippfind\fR supports the following registration types: +.TP 5 +_http._tcp +HyperText Transport Protocol (HTTP, RFC 2616) +.TP 5 +_https._tcp +Secure HyperText Transport Protocol (HTTPS, RFC 2818) +.TP 5 +_ipp._tcp +Internet Printing Protocol (IPP, RFC 2911) +.TP 5 +_ipps._tcp +Secure Internet Printing Protocol (IPPS, draft) +.TP 5 +_printer._tcp +Line Printer Daemon (LPD, RFC 1179) + +.SH OPTIONS +.TP 5 +--help +Show program help +.TP 5 +--version +Show program version +.TP 5 +-4 +Use IPv4 when listing +.TP 5 +-6 +Use IPv6 when listing +.TP 5 +-T seconds +Specify find timeout in seconds. If 1 or less, \fIippfind\fR stops as soon as it thinks it has found everything. The default is 1 second. +.TP 5 +-V version +Specifies the IPP version when listing. Supported values are 1.1, 2.0, 2.1, and 2.2. + +.SH DESCRIPTION +\fIippfind\fR finds printer services registered with the local DNS infrastructure or available through the local links. + +.SH EXPRESSIONS +\fIippfind\fR supports expressions much like the \fIfind(1)\fR utility. However, unlike \fIfind\fR, \fIippfind\fR uses POSIX regular expressions instead of shell filename matching patterns. If -e, --exec, -l, --ls, -p, --print, --print-name, -q, --quiet, or -s is not specified, \fIippfind\fR adds --print to print the service URI of anything it finds. The following expressions are supported: +.TP 5 +-d regex +.TP 5 +--domain regex +True if the domain matches the given regular expression. +.TP 5 +-e utility [argument ...] ; +.TP 5 +--exec utility [argument ...] ; +Executes the specified program if the current result is true. "{foo}" arguments are replaced with the corresponding value - see SUBSTITUTIONS below. +.TP 5 +--false +Always false. +.TP 5 +-l +.TP 5 +--ls +Lists attributes returned by Get-Printer-Attributes for IPP printers and traditional \fIfind\fR "-ls" output for HTTP URLs. The result is true if the URI is accessible, false otherwise. +.TP 5 +--local +True if the service is local to this computer. +.TP 5 +-n regex +.TP 5 +--name regex +True if the service instance name matches the given regular expression. +.TP 5 +--path regex +True if the URI resource path matches the given regular expression. +.TP 5 +-p +.TP 5 +--print +Prints the URI if the result of previous expressions is true. The result is always true. +.TP 5 +-q +.TP 5 +--quiet +Quiet mode - just returns the exit codes below. +.TP 5 +-r +.TP 5 +--remote +True if the service is not local to this computer. +.TP 5 +-s +.TP 5 +--print-name +Prints the service instance name if the result of previous expressions is true. The result is always true. +.TP 5 +--true +Always true. +.TP 5 +-t key +.TP 5 +--txt key +True if the TXT record contains the named key. +.TP 5 +--txt-\fIkey\fR regex +True if the TXT record contains the named key and matches the given regular +expression. +.TP 5 +-u regex +.TP 5 +--uri regex +True if the URI matches the given regular expression. +.PP +Expressions may also contain modifiers: +.TP 5 +( expression ) +Group the result of expressions. +.TP 5 +! expression +.TP 5 +--not expression +Unary NOT of the expression. +.TP 5 +expression expression +.TP 5 +expression --and expression +Logical AND of expressions. +.TP 5 +expression --or expression +Logical OR of expressions. + +.SH SUBSTITUTIONS +The substitutions for "{foo}" in -e and --exec are: +.TP 5 +{} +URI +.TP 5 +{service_domain} +Domain name, e.g., "example.com.", "local.", etc. +.TP 5 +{service_hostname} +Fully-qualified domain name, e.g., "printer.example.com.", "printer.local.", etc. +.TP 5 +{service_name} +Service instance name, e.g., "My Fine Printer". +.TP 5 +{service_port} +Port number for server, typically 631 for IPP and 80 for HTTP. +.TP 5 +{service_regtype} +DNS-SD registration type, e.g., "_ipp._tcp", "_http._tcp", etc. +.TP 5 +{service_scheme} +URI scheme for DNS-SD registration type, e.g., "ipp", "http", etc. +.TP 5 +{service_uri} +URI for service, e.g., "ipp://printer.local./ipp/print", "http://printer.local./", etc. +.TP 5 +{txt_\fIkey\fR} +Value of TXT record \fIkey\fR (lowercase). + +.SH ENVIRONMENT VARIABLES +When executing a program, \fIippfind\fR sets the following environment variables for the matching service registration: +.TP 5 +IPPFIND_SERVICE_DOMAIN +Domain name, e.g., "example.com.", "local.", etc. +.TP 5 +IPPFIND_SERVICE_HOSTNAME +Fully-qualified domain name, e.g., "printer.example.com.", "printer.local.", etc. +.TP 5 +IPPFIND_SERVICE_NAME +Service instance name, e.g., "My Fine Printer". +.TP 5 +IPPFIND_SERVICE_PORT +Port number for server, typically 631 for IPP and 80 for HTTP. +.TP 5 +IPPFIND_SERVICE_REGTYPE +DNS-SD registration type, e.g., "_ipp._tcp", "_http._tcp", etc. +.TP 5 +IPPFIND_SERVICE_SCHEME +URI scheme for DNS-SD registration type, e.g., "ipp", "http", etc. +.TP 5 +IPPFIND_SERVICE_URI +URI for service, e.g., "ipp://printer.local./ipp/print", "http://printer.local./", etc. +.TP 5 +IPPFIND_TXT_\fIKEY\fR +Values of TXT record \fIKEY\fR (uppercase). + +.SH EXIT CODES +\fIippfind\fR returns 0 if the result for all processed expressions is true, 1 if the result of any processed expression is false, 2 if browsing or any query or resolution failed, 3 if an undefined option or invalid expression was specified, and 4 if it ran out of memory. + +.SH SEE ALSO +\fIipptool(1)\fR + +.SH COPYRIGHT +Copyright 2013 by Apple Inc. +.\" +.\" End of "$Id$". +.\" diff --git a/test/Dependencies b/test/Dependencies index 69b96a6f4..9686a1f66 100644 --- a/test/Dependencies +++ b/test/Dependencies @@ -1,18 +1,23 @@ -ippserver.o: ippserver.c ../cups/cups-private.h ../cups/string-private.h \ +ippfind.o: ippfind.c ../cups/cups-private.h ../cups/string-private.h \ ../config.h ../cups/debug-private.h ../cups/versioning.h \ - ../cups/ipp-private.h ../cups/ipp.h ../cups/http.h ../cups/array.h \ - ../cups/http-private.h ../cups/md5-private.h \ - ../cups/language-private.h ../cups/transcode.h ../cups/language.h \ - ../cups/pwg-private.h ../cups/cups.h ../cups/file.h \ - ../cups/ppd-private.h ../cups/ppd.h ../cups/thread-private.h + ../cups/array-private.h ../cups/array.h ../cups/ipp-private.h \ + ../cups/ipp.h ../cups/http.h ../cups/http-private.h \ + ../cups/md5-private.h ../cups/language-private.h ../cups/transcode.h \ + ../cups/language.h ../cups/pwg-private.h ../cups/cups.h ../cups/file.h \ + ../cups/pwg.h ../cups/ppd-private.h ../cups/ppd.h \ + ../cups/thread-private.h +ippserver.o: ippserver.c ../cups/cups.h ../cups/file.h \ + ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h \ + ../cups/language.h ../cups/pwg.h ../config.h ../cups/string-private.h \ + ../cups/thread-private.h ipptool.o: ipptool.c ../cups/cups-private.h ../cups/string-private.h \ ../config.h ../cups/debug-private.h ../cups/versioning.h \ - ../cups/ipp-private.h ../cups/ipp.h ../cups/http.h ../cups/array.h \ - ../cups/http-private.h ../cups/md5-private.h \ - ../cups/language-private.h ../cups/transcode.h ../cups/language.h \ - ../cups/pwg-private.h ../cups/cups.h ../cups/file.h \ - ../cups/ppd-private.h ../cups/ppd.h ../cups/thread-private.h \ - ../cups/file-private.h + ../cups/array-private.h ../cups/array.h ../cups/ipp-private.h \ + ../cups/ipp.h ../cups/http.h ../cups/http-private.h \ + ../cups/md5-private.h ../cups/language-private.h ../cups/transcode.h \ + ../cups/language.h ../cups/pwg-private.h ../cups/cups.h ../cups/file.h \ + ../cups/pwg.h ../cups/ppd-private.h ../cups/ppd.h \ + ../cups/thread-private.h ../cups/file-private.h xmltotest.o: xmltotest.c ../config.h ../cups/cups.h ../cups/file.h \ ../cups/versioning.h ../cups/ipp.h ../cups/http.h ../cups/array.h \ - ../cups/language.h + ../cups/language.h ../cups/pwg.h diff --git a/test/Makefile b/test/Makefile index c7c5c8e99..c7507d9cb 100644 --- a/test/Makefile +++ b/test/Makefile @@ -53,10 +53,12 @@ TESTFILES = \ print-job-gzip.test \ validate-job.test OBJS = \ + ippfind.o \ ippserver.o \ ipptool.o \ xmltotest.o TARGETS = \ + $(IPPFIND_BIN) \ ippserver \ ipptool \ ipptool-static @@ -165,6 +167,18 @@ uninstall: -$(RMDIR) $(DATADIR)/ipptool +# +# ippfind +# + +ippfind: ippfind.o ../cups/$(LIBCUPS) ../cups/$(LIBCUPSSTATIC) + echo Linking $@... + $(CC) $(LDFLAGS) -o $@ ippfind.o $(LIBS) + echo Linking $@-static... + $(CC) $(LDFLAGS) -o $@-static ippfind.o ../cups/$(LIBCUPSSTATIC) \ + $(LIBGSSAPI) $(SSLLIBS) $(DNSSDLIBS) $(COMMONLIBS) $(LIBZ) + + # # ippserver # diff --git a/test/ippfind.c b/test/ippfind.c index 73f395c26..44223d69e 100644 --- a/test/ippfind.c +++ b/test/ippfind.c @@ -113,10 +113,8 @@ * --true - Always true * * expression expression - * expression --and expression - * expression -a expression - Logical AND. + * expression --and expression - Logical AND. * - * expression -o expression * expression --or expression - Logical OR. * * The substitutions for {} are: @@ -171,10 +169,11 @@ typedef enum ippfind_exit_e /* Exit codes */ { - IPPFIND_EXIT_OK = 0, /* OK and result is true */ + IPPFIND_EXIT_TRUE = 0, /* OK and result is true */ IPPFIND_EXIT_FALSE, /* OK but result is false*/ IPPFIND_EXIT_BONJOUR, /* Browse/resolve failure */ - IPPFIND_EXIT_SYNTAX /* Bad option or syntax error */ + IPPFIND_EXIT_SYNTAX, /* Bad option or syntax error */ + IPPFIND_EXIT_MEMORY /* Out of memory */ } ippfind_exit_t; typedef enum ippfind_op_e /* Operations for expressions */ @@ -184,6 +183,8 @@ typedef enum ippfind_op_e /* Operations for expressions */ IPPFIND_OP_OR, /* Logical OR of all children */ IPPFIND_OP_TRUE, /* Always true */ IPPFIND_OP_FALSE, /* Always false */ + IPPFIND_OP_IS_LOCAL, /* Is a local service */ + IPPFIND_OP_IS_REMOTE, /* Is a remote service */ IPPFIND_OP_DOMAIN_REGEX, /* Domain matches regular expression */ IPPFIND_OP_NAME_REGEX, /* Name matches regular expression */ IPPFIND_OP_PATH_REGEX, /* Path matches regular expression */ @@ -210,6 +211,8 @@ typedef struct ippfind_expr_s /* Expression */ int invert; /* Invert the result */ char *key; /* TXT record key */ regex_t re; /* Regular expression for matching */ + int num_args; /* Number of arguments for exec */ + char **args; /* Arguments for exec */ } ippfind_expr_t; typedef struct ippfind_srv_s /* Service information */ @@ -231,7 +234,6 @@ typedef struct ippfind_srv_s /* Service information */ is_local, /* Is a local service? */ is_processed, /* Did we process the service? */ is_resolved; /* Got the resolve data? */ - time_t resolve_time; /* Time we started the resolve */ } ippfind_srv_t; @@ -251,8 +253,8 @@ static AvahiSimplePoll *avahi_poll = NULL; static int address_family = AF_UNSPEC; /* Address family for LIST */ static int bonjour_error = 0; /* Error browsing/resolving? */ +static double bonjour_timeout = 1.0; /* Timeout in seconds */ static int ipp_version = 20; /* IPP version for LIST */ -static double timeout = 10; /* Timeout in seconds */ /* @@ -293,11 +295,17 @@ static void client_callback(AvahiClient *client, #endif /* HAVE_AVAHI */ static int compare_services(ippfind_srv_t *a, ippfind_srv_t *b); +static const char *dnssd_error_string(int error); +static int eval_expr(ippfind_srv_t *service, + ippfind_expr_t *expressions); static ippfind_srv_t *get_service(cups_array_t *services, const char *serviceName, const char *regtype, const char *replyDomain) __attribute__((nonnull(1,2,3,4))); +static double get_time(void); +static ippfind_expr_t *new_expr(ippfind_op_t op, int invert, const char *key, + const char *regex, char **args); #ifdef HAVE_DNSSD static void resolve_callback(DNSServiceRef sdRef, DNSServiceFlags flags, @@ -307,7 +315,7 @@ static void resolve_callback(DNSServiceRef sdRef, const char *hostTarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, - void *context); + void *context) __attribute__((nonnull(1,5,6,9, 10))); #elif defined(HAVE_AVAHI) static int poll_callback(struct pollfd *pollfds, @@ -329,8 +337,6 @@ static void resolve_callback(AvahiServiceResolver *res, static void set_service_uri(ippfind_srv_t *service); static void show_usage(void) __attribute__((noreturn)); static void show_version(void) __attribute__((noreturn)); -static void unquote(char *dst, const char *src, size_t dstsize) - __attribute__((nonnull(1,2))); /* @@ -341,9 +347,29 @@ int /* O - Exit status */ main(int argc, /* I - Number of command-line args */ char *argv[]) /* I - Command-line arguments */ { - int fd; /* File descriptor for Bonjour */ - cups_array_t *services; /* Service array */ - ippfind_srv_t *service; /* Current service */ + int i, /* Looping var */ + have_output = 0,/* Have output expression */ + status = IPPFIND_EXIT_TRUE; + /* Exit status */ + const char *opt, /* Option character */ + *search; /* Current browse/resolve string */ + cups_array_t *searches; /* Things to browse/resolve */ + cups_array_t *services; /* Service array */ + ippfind_srv_t *service; /* Current service */ + ippfind_expr_t *expressions = NULL, + /* Expression tree */ + *temp = NULL, /* New expression */ + *parent = NULL, /* Parent expression */ + *current = NULL;/* Current expression */ + ippfind_op_t logic = IPPFIND_OP_AND; + /* Logic for next expression */ + int invert = 0; /* Invert expression? */ + int err; /* DNS-SD error */ +#ifdef HAVE_DNSSD + fd_set sinput; /* Input set for select() */ + struct timeval stimeout; /* Timeout for select() */ +#endif /* HAVE_DNSSD */ + double endtime; /* End time */ /* @@ -353,409 +379,868 @@ main(int argc, /* I - Number of command-line args */ _cupsSetLocale(argv); /* - * Create an array to track services... + * Create arrays to track services and things we want to browse/resolve... */ + searches = cupsArrayNew(NULL, NULL); services = cupsArrayNew((cups_array_func_t)compare_services, NULL); /* - * Start up masters for browsing/resolving... + * Parse command-line... */ -#ifdef HAVE_DNSSD - if (DNSServiceCreateConnection(&dnssd_ref) != kDNSServiceErr_NoError) + for (i = 1; i < argc; i ++) { - perror("ERROR: Unable to create service connection"); - return (IPPFIND_EXIT_BONJOUR); - } + if (argv[i][0] == '-') + { + if (argv[i][1] == '-') + { + /* + * Parse --option options... + */ + + if (!strcmp(argv[i], "--and")) + { + if (logic != IPPFIND_OP_AND && current && current->prev) + { + /* + * OK, we have more than 1 rule in the current tree level. + * Make a new group tree and move the previous rule to it... + */ + + if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + temp->child = current; + temp->parent = current->parent; + current->prev->next = temp; + temp->prev = current->prev; + + current->prev = NULL; + current->parent = temp; + parent = temp; + } + else if (parent) + parent->op = IPPFIND_OP_AND; - fd = DNSServiceRefSockFD(dnssd_ref); + logic = IPPFIND_OP_AND; + temp = NULL; + } + else if (!strcmp(argv[i], "--domain")) + { + i ++; + if (i >= argc) + show_usage(); -#elif defined(HAVE_AVAHI) -#endif /* HAVE_DNSSD */ + if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, argv[i], + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--exec")) + { + i ++; + if (i >= argc) + show_usage(); -#if 0 - int i; /* Looping var */ - DNSServiceRef main_ref, /* Main service reference */ - ipp_ref; /* IPP service reference */ - int fd; /* Main file descriptor */ - fd_set input; /* Input set for select() */ - struct timeval timeout; /* Timeout for select() */ - cups_array_t *devices; /* Device array */ - ippfind_srv_t *device; /* Current device */ - http_t *http; /* Connection to printer */ - ipp_t *request, /* Get-Printer-Attributes request */ - *response; /* Get-Printer-Attributes response */ - ipp_attribute_t *attr; /* IPP attribute in response */ - const char *version, /* Version supported */ - *testfile; /* Test file to use */ - int ipponly = 0, /* Do IPP tests only? */ - snmponly = 0; /* Do SNMP walk only? */ + if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL, + argv + i)) == NULL) + return (IPPFIND_EXIT_MEMORY); + while (i < argc) + if (!strcmp(argv[i], ";")) + break; + else + i ++; - for (i = 1; i < argc; i ++) - if (!strcmp(argv[i], "snmp")) - snmponly = 1; - else if (!strcmp(argv[i], "ipp")) - ipponly = 1; - else + if (i >= argc) + show_usage(); + + have_output = 1; + } + else if (!strcmp(argv[i], "--false")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_FALSE, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--help")) + { + show_usage(); + } + else if (!strcmp(argv[i], "--ls")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + } + else if (!strcmp(argv[i], "--local")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_IS_LOCAL, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--name")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, argv[i], + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--not")) + { + invert = 1; + } + else if (!strcmp(argv[i], "--or")) + { + if (logic != IPPFIND_OP_OR && current) + { + /* + * OK, we have two possibilities; either this is the top-level + * rule or we have a bunch of AND rules at this level. + */ + + if (!parent) + { + /* + * This is the top-level rule; we have to group *all* of the AND + * rules down a level, as AND has precedence over OR. + */ + + if ((temp = new_expr(IPPFIND_OP_AND, 0, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + while (current->prev) + { + current->parent = temp; + current = current->prev; + } + + current->parent = temp; + temp->child = current; + + expressions = current = temp; + } + else + { + /* + * This isn't the top rule, so go up one level... + */ + + current = parent; + parent = current->parent; + } + } + + logic = IPPFIND_OP_OR; + temp = NULL; + } + else if (!strcmp(argv[i], "--path")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_PATH_REGEX, invert, NULL, argv[i], + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--print")) + { + if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + } + else if (!strcmp(argv[i], "--print-name")) + { + if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + } + else if (!strcmp(argv[i], "--quiet")) + { + if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + } + else if (!strcmp(argv[i], "--remote")) + { + if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--true")) + { + if ((temp = new_expr(IPPFIND_OP_TRUE, invert, NULL, argv[i], + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--txt")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strncmp(argv[i], "--txt-", 5)) + { + const char *key = argv[i] + 5;/* TXT key */ + + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_TXT_REGEX, invert, key, argv[i], + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--uri")) + { + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, argv[i], + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + } + else if (!strcmp(argv[i], "--version")) + { + show_version(); + } + else + { + _cupsLangPrintf(stderr, _("%s: Unknown option \"%s\"."), + "ippfind", argv[i]); + show_usage(); + } + + if (temp) + { + /* + * Add new expression... + */ + + if (current) + { + temp->parent = parent; + current->next = temp; + } + else if (parent) + parent->child = temp; + else + expressions = temp; + + temp->prev = current; + current = temp; + invert = 0; + temp = NULL; + } + } + else + { + /* + * Parse -o options + */ + + for (opt = argv[i] + 1; *opt; opt ++) + { + switch (*opt) + { + case '4' : + address_family = AF_INET; + break; + + case '6' : + address_family = AF_INET6; + break; + + case 'T' : + i ++; + if (i >= argc) + show_usage(); + + bonjour_timeout = atof(argv[i]); + break; + + case 'V' : + i ++; + if (i >= argc) + show_usage(); + + if (!strcmp(argv[i], "1.1")) + ipp_version = 11; + else if (!strcmp(argv[i], "2.0")) + ipp_version = 20; + else if (!strcmp(argv[i], "2.1")) + ipp_version = 21; + else if (!strcmp(argv[i], "2.2")) + ipp_version = 22; + else + show_usage(); + break; + + case 'd' : + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_DOMAIN_REGEX, invert, NULL, + argv[i], NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + break; + + case 'e' : + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_EXEC, invert, NULL, NULL, + argv + i)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + while (i < argc) + if (!strcmp(argv[i], ";")) + break; + else + i ++; + + if (i >= argc) + show_usage(); + + have_output = 1; + break; + + case 'l' : + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_LIST, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + break; + + case 'n' : + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_NAME_REGEX, invert, NULL, + argv[i], NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + break; + + case 'p' : + if ((temp = new_expr(IPPFIND_OP_PRINT_URI, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + break; + + case 'q' : + if ((temp = new_expr(IPPFIND_OP_QUIET, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + break; + + case 'r' : + if ((temp = new_expr(IPPFIND_OP_IS_REMOTE, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + break; + + case 's' : + if ((temp = new_expr(IPPFIND_OP_PRINT_NAME, invert, NULL, NULL, + NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + have_output = 1; + break; + + case 't' : + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_TXT_EXISTS, invert, argv[i], + NULL, NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + break; + + case 'u' : + i ++; + if (i >= argc) + show_usage(); + + if ((temp = new_expr(IPPFIND_OP_URI_REGEX, invert, NULL, + argv[i], NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + break; + + default : + _cupsLangPrintf(stderr, _("%s: Unknown option \"-%c\"."), + "ippfind", *opt); + show_usage(); + break; + } + + if (temp) + { + /* + * Add new expression... + */ + + if (current) + { + temp->parent = parent; + current->next = temp; + } + else if (parent) + parent->child = temp; + else + expressions = temp; + + temp->prev = current; + current = temp; + invert = 0; + temp = NULL; + } + } + } + } + else if (!strcmp(argv[i], "(")) { - puts("Usage: ./ipp-printers [{ipp | snmp}]"); - return (1); + if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + if (current) + { + temp->parent = current->parent; + current->next = temp; + } + else + expressions = temp; + + temp->prev = current; + parent = temp; + current = NULL; + invert = 0; + logic = IPPFIND_OP_AND; } + else if (!strcmp(argv[i], ")")) + { + if (!parent) + { + _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis.")); + show_usage(); + } - /* - * Create an array to track devices... - */ + current = parent; + parent = current->parent; - devices = cupsArrayNew((cups_array_func_t)compare_services, NULL); + if (!parent) + logic = IPPFIND_OP_AND; + else + logic = parent->op; + } + else if (!strcmp(argv[i], "!")) + { + invert = 1; + } + else + { + /* + * _regtype._tcp[,subtype][.domain] + * + * OR + * + * service-name[._regtype._tcp[.domain]] + */ - /* - * Browse for different kinds of printers... - */ + cupsArrayAdd(searches, argv[i]); + } + } + + if (parent) + { + _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis.")); + show_usage(); + } - if (DNSServiceCreateConnection(&main_ref) != kDNSServiceErr_NoError) + if (cupsArrayCount(searches) == 0) { - perror("ERROR: Unable to create service connection"); - return (1); + /* + * Add an implicit browse for IPP printers ("_ipp._tcp")... + */ + + cupsArrayAdd(searches, "_ipp._tcp"); } - fd = DNSServiceRefSockFD(main_ref); + if (!have_output) + { + /* + * Add an implicit --print-uri to the end... + */ + + if ((temp = new_expr(IPPFIND_OP_PRINT_URI, 0, NULL, NULL, NULL)) == NULL) + return (IPPFIND_EXIT_MEMORY); + + if (current) + { + temp->parent = parent; + current->next = temp; + } + else + expressions = temp; - ipp_ref = main_ref; - DNSServiceBrowse(&ipp_ref, kDNSServiceFlagsShareConnection, 0, - "_ipp._tcp", NULL, browse_callback, devices); + temp->prev = current; + } /* - * Loop until we are killed... + * Start up browsing/resolving... */ - progress(); +#ifdef HAVE_DNSSD + if ((err = DNSServiceCreateConnection(&dnssd_ref)) != kDNSServiceErr_NoError) + { + _cupsLangPrintf(stderr, _("ippfind: Unable to use Bonjour: %s"), + dnssd_error_string(err)); + return (IPPFIND_EXIT_BONJOUR); + } - for (;;) +#elif defined(HAVE_AVAHI) + if ((avahi_poll = avahi_simple_poll_new()) == NULL) { - FD_ZERO(&input); - FD_SET(fd, &input); + _cupsLangPrintError(stderr, _("ippfind: Unable to use Bonjour: %s"), + strerror(errno)); + return (IPPFIND_EXIT_BONJOUR); + } - timeout.tv_sec = 2; - timeout.tv_usec = 500000; + avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL); - if (select(fd + 1, &input, NULL, NULL, &timeout) <= 0) - { - time_t curtime = time(NULL); + avahi_client = avahi_client_new(avahi_simple_poll_get(avahi_poll), + 0, client_callback, avahi_poll, &err); + if (!client) + { + _cupsLangPrintError(stderr, _("ippfind: Unable to use Bonjour: %s"), + dnssd_error_string(err)); + return (IPPFIND_EXIT_BONJOUR); + } +#endif /* HAVE_DNSSD */ - for (device = (ippfind_srv_t *)cupsArrayFirst(devices); - device; - device = (ippfind_srv_t *)cupsArrayNext(devices)) - if (!device->got_resolve) - { - if (!device->ref) - break; + for (search = (const char *)cupsArrayFirst(searches); + search; + search = (const char *)cupsArrayNext(searches)) + { + char buf[1024], /* Full name string */ + *name = NULL, /* Service instance name */ + *regtype, /* Registration type */ + *domain; /* Domain, if any */ - if ((curtime - device->resolve_time) > 10) - { - device->got_resolve = -1; - fprintf(stderr, "\rUnable to resolve \"%s\": timeout\n", - device->name); - progress(); - } - else - break; - } + strlcpy(buf, search, sizeof(buf)); + if (buf[0] == '_') + { + regtype = buf; + } + else if ((regtype = strstr(buf, "._")) != NULL) + { + name = buf; + *regtype++ = '\0'; + } + else + { + name = buf; + regtype = "_ipp._tcp"; + } - if (!device) + for (domain = regtype; *domain; domain ++) + if (*domain == '.' && domain[1] != '_') + { + *domain++ = '\0'; break; - } + } - if (FD_ISSET(fd, &input)) + if (!*domain) + domain = NULL; + + if (name) { /* - * Process results of our browsing... + * Resolve the given service instance name, regtype, and domain... */ - progress(); - DNSServiceProcessResult(main_ref); + if (!domain) + domain = "local."; + + service = get_service(services, name, regtype, domain); + +#ifdef HAVE_DNSSD + service->ref = dnssd_ref; + err = DNSServiceResolve(&(service->ref), + kDNSServiceFlagsShareConnection, 0, name, + regtype, domain, resolve_callback, + service); + +#elif defined(HAVE_AVAHI) + service->ref = avahi_service_resolver_new(avahi_client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, name, + regtype, domain, + AVAHI_PROTO_UNSPEC, 0, + resolve_callback, service); + if (service->ref) + err = 0; + else + err = avahi_client_get_errno(avahi_client); +#endif /* HAVE_DNSSD */ } else { /* - * Query any devices we've found... + * Browse for services of the given type... */ - DNSServiceErrorType status; /* DNS query status */ - int count; /* Number of queries */ +#ifdef HAVE_DNSSD + DNSServiceRef ref; /* Browse reference */ + ref = dnssd_ref; + err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, 0, regtype, + domain, browse_callback, services); - for (device = (ippfind_srv_t *)cupsArrayFirst(devices), count = 0; - device; - device = (ippfind_srv_t *)cupsArrayNext(devices)) + if (!err) { - if (!device->ref && !device->sent) - { - /* - * Found the device, now get the TXT record(s) for it... - */ + ref = dnssd_ref; + err = DNSServiceBrowse(&ref, kDNSServiceFlagsShareConnection, + kDNSServiceInterfaceIndexLocalOnly, regtype, + domain, browse_local_callback, services); + } - 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... - */ +#elif defined(HAVE_AVAHI) + if (avahi_service_browser_new(avahi_client, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, regtype, domain, 0, + browse_callback, services)) + err = 0; + else + err = avahi_client_get_errno(avahi_client); +#endif /* HAVE_DNSSD */ + } - DNSServiceRefDeallocate(device->ref); - device->ref = 0; - device->sent = 1; - } - } + if (err) + { + _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"), + dnssd_error_string(err)); + + if (name) + printf("name=\"%s\"\n", name); + + printf("regtype=\"%s\"\n", regtype); + + if (domain) + printf("domain=\"%s\"\n", domain); + + return (IPPFIND_EXIT_BONJOUR); } } -#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 = (ippfind_srv_t *)cupsArrayFirst(devices); - device; - device = (ippfind_srv_t *)cupsArrayNext(devices)) + /* + * Process browse/resolve requests... + */ + + if (bonjour_timeout > 1.0) + endtime = get_time() + bonjour_timeout; + else + endtime = get_time() + 300.0; + + while (get_time() < endtime) { - if (device->got_resolve <= 0 || device->cups_shared) + int process = 0; /* Process services? */ + +#ifdef HAVE_DNSSD + int fd = DNSServiceRefSockFD(dnssd_ref); + /* File descriptor for DNS-SD */ + + FD_ZERO(&sinput); + FD_SET(fd, &sinput); + + stimeout.tv_sec = 0; + stimeout.tv_usec = 500000; + + if (select(fd + 1, &sinput, NULL, NULL, &stimeout) < 0) 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 (FD_ISSET(fd, &sinput)) + { + /* + * Process responses... + */ - if ((http = httpConnect(device->host, device->port)) == NULL) + DNSServiceProcessResult(dnssd_ref); + } + else { - fprintf(stderr, "Failed to connect to \"%s\": %s\n", device->name, - cupsLastErrorString()); - continue; + /* + * Time to process services... + */ + + process = 1; } - request = ippNewRequest(IPP_GET_PRINTER_ATTRIBUTES); - ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, - device->uri); +#elif defined(HAVE_AVAHI) + avahi_got_data = 0; - response = cupsDoRequest(http, request, device->rp); + if (avahi_simple_poll_iterate(avahi_poll, 500) > 0) + { + /* + * We've been told to exit the loop. Perhaps the connection to + * Avahi failed. + */ - if (cupsLastError() > IPP_OK_SUBST) - fprintf(stderr, "Failed to query \"%s\": %s\n", device->name, - cupsLastErrorString()); - else + return (IPPFIND_EXIT_BONJOUR); + } + + if (!avahi_got_data) { - if ((attr = ippFindAttribute(response, "ipp-versions-supported", - IPP_TAG_KEYWORD)) != NULL) - { - version = attr->values[0].string.text; + /* + * Time to process services... + */ - 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"; + process = 1; + } +#endif /* HAVE_DNSSD */ - testfile = NULL; + if (process) + { + /* + * Process any services that we have found... + */ - if ((attr = ippFindAttribute(response, "document-format-supported", - IPP_TAG_MIMETYPE)) != NULL) + int active = 0, /* Number of active resolves */ + resolved = 0, /* Number of resolved services */ + processed = 0; /* Number of processed services */ + + for (service = (ippfind_srv_t *)cupsArrayFirst(services); + service; + service = (ippfind_srv_t *)cupsArrayNext(services)) { - /* - * Figure out the test file for printing, preferring PDF and PostScript - * over JPEG and plain text... - */ + if (service->is_processed) + processed ++; - 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 (service->is_resolved) + resolved ++; - if (!testfile) + if (!service->ref && !service->is_resolved) { - 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 */ + /* + * Found a service, now resolve it (but limit to 50 active resolves...) + */ + if (active < 50) + { +#ifdef HAVE_DNSSD + service->ref = dnssd_ref; + err = DNSServiceResolve(&(service->ref), + kDNSServiceFlagsShareConnection, 0, + service->name, service->regtype, + service->domain, resolve_callback, + service); - pdl = strdup(device->pdl); - for (start = device->pdl; start && *start; start = end) - { - if ((end = strchr(start, ',')) != NULL) - *end++ = '\0'; +#elif defined(HAVE_AVAHI) + service->ref = avahi_service_resolver_new(avahi_client, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + service->name, + service->regtype, + service->domain, + AVAHI_PROTO_UNSPEC, 0, + resolve_callback, + service); + if (service->ref) + err = 0; + else + err = avahi_client_get_errno(avahi_client); +#endif /* HAVE_DNSSD */ - 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 (err) + { + _cupsLangPrintf(stderr, + _("ippfind: Unable to browse or resolve: %s"), + dnssd_error_string(err)); + return (IPPFIND_EXIT_BONJOUR); + } - if (testfile) - { - fprintf(stderr, - "Using \"%s\" for printer \"%s\" based on TXT record pdl " - "info.\n", testfile, device->name); + active ++; + } } - else + else if (service->is_resolved && !service->is_processed) { - 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); + /* + * Resolved, not process this service against the expressions... + */ - if (!strncasecmp(device->ty, "Lexmark International Lexmark", 29)) - typtr = device->ty + 22; - else - typtr = device->ty; + if (service->ref) + { +#ifdef HAVE_DNSSD + DNSServiceRefDeallocate(service->ref); +#else + avahi_record_browser_free(service->ref); +#endif /* HAVE_DNSSD */ - while (*typtr && fileptr < (filename + sizeof(filename) - 1)) - { - if (isalnum(*typtr & 255) || *typtr == '-') - *fileptr++ = *typtr++; - else - { - *fileptr++ = '_'; - typtr++; + service->ref = NULL; } - } - - *fileptr = '\0'; - printf("# %s\n", device->name); - printf("echo \"Testing %s...\"\n", device->name); + if (!eval_expr(service, expressions)) + status = IPPFIND_EXIT_FALSE; - 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); + service->is_processed = 1; } + else if (service->ref) + active ++; + } - 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); - } + /* + * If we have processed all services we have discovered, then we are done. + */ - 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); + if (processed == cupsArrayCount(services) && bonjour_timeout <= 1.0) + break; } - - ippDelete(response); - httpClose(http); } - return (0); -#endif /* 0 */ + if (bonjour_error) + return (IPPFIND_EXIT_BONJOUR); + else + return (status); } @@ -920,6 +1405,137 @@ compare_services(ippfind_srv_t *a, /* I - First device */ } +/* + * 'dnssd_error_string()' - Return an error string for an error code. + */ + +static const char * /* O - Error message */ +dnssd_error_string(int error) /* I - Error number */ +{ +# ifdef HAVE_DNSSD + switch (error) + { + case kDNSServiceErr_NoError : + return ("OK."); + + default : + case kDNSServiceErr_Unknown : + return ("Unknown error."); + + case kDNSServiceErr_NoSuchName : + return ("Service not found."); + + case kDNSServiceErr_NoMemory : + return ("Out of memory."); + + case kDNSServiceErr_BadParam : + return ("Bad parameter."); + + case kDNSServiceErr_BadReference : + return ("Bad service reference."); + + case kDNSServiceErr_BadState : + return ("Bad state."); + + case kDNSServiceErr_BadFlags : + return ("Bad flags."); + + case kDNSServiceErr_Unsupported : + return ("Unsupported."); + + case kDNSServiceErr_NotInitialized : + return ("Not initialized."); + + case kDNSServiceErr_AlreadyRegistered : + return ("Already registered."); + + case kDNSServiceErr_NameConflict : + return ("Name conflict."); + + case kDNSServiceErr_Invalid : + return ("Invalid name."); + + case kDNSServiceErr_Firewall : + return ("Firewall prevents registration."); + + case kDNSServiceErr_Incompatible : + return ("Client library incompatible."); + + case kDNSServiceErr_BadInterfaceIndex : + return ("Bad interface index."); + + case kDNSServiceErr_Refused : + return ("Server prevents registration."); + + case kDNSServiceErr_NoSuchRecord : + return ("Record not found."); + + case kDNSServiceErr_NoAuth : + return ("Authentication required."); + + case kDNSServiceErr_NoSuchKey : + return ("Encryption key not found."); + + case kDNSServiceErr_NATTraversal : + return ("Unable to traverse NAT boundary."); + + case kDNSServiceErr_DoubleNAT : + return ("Unable to traverse double-NAT boundary."); + + case kDNSServiceErr_BadTime : + return ("Bad system time."); + + case kDNSServiceErr_BadSig : + return ("Bad signature."); + + case kDNSServiceErr_BadKey : + return ("Bad encryption key."); + + case kDNSServiceErr_Transient : + return ("Transient error occurred - please try again."); + + case kDNSServiceErr_ServiceNotRunning : + return ("Server not running."); + + case kDNSServiceErr_NATPortMappingUnsupported : + return ("NAT doesn't support NAT-PMP or UPnP."); + + case kDNSServiceErr_NATPortMappingDisabled : + return ("NAT supports NAT-PNP or UPnP but it is disabled."); + + case kDNSServiceErr_NoRouter : + return ("No Internet/default router configured."); + + case kDNSServiceErr_PollingMode : + return ("Service polling mode error."); + + case kDNSServiceErr_Timeout : + return ("Service timeout."); + } + +# elif defined(HAVE_AVAHI) + return (avahi_strerror(error)); +# endif /* HAVE_DNSSD */ +} + + +/* + * 'eval_expr()' - Evaluate the expressions against the specified service. + * + * Returns 1 for true and 0 for false. + */ + +static int /* O - Result of evaluation */ +eval_expr(ippfind_srv_t *service, /* I - Service */ + ippfind_expr_t *expressions) /* I - Expressions */ +{ + (void)expressions; + + puts(service->uri); + return (1); +} + + /* * 'get_service()' - Create or update a device. */ @@ -980,6 +1596,83 @@ get_service(cups_array_t *services, /* I - Service array */ } +/* + * 'get_time()' - Get the current time-of-day in seconds. + */ + +static double +get_time(void) +{ +#ifdef WIN32 + struct _timeb curtime; /* Current Windows time */ + + _ftime(&curtime); + + return (curtime.time + 0.001 * curtime.millitm); + +#else + struct timeval curtime; /* Current UNIX time */ + + if (gettimeofday(&curtime, NULL)) + return (0.0); + else + return (curtime.tv_sec + 0.000001 * curtime.tv_usec); +#endif /* WIN32 */ +} + + +/* + * 'new_expr()' - Create a new expression. + */ + +static ippfind_expr_t * /* O - New expression */ +new_expr(ippfind_op_t op, /* I - Operation */ + int invert, /* I - Invert result? */ + const char *key, /* I - TXT key */ + const char *regex, /* I - Regular expression */ + char **args) /* I - Pointer to argument strings */ +{ + ippfind_expr_t *temp; /* New expression */ + + + if ((temp = calloc(1, sizeof(ippfind_expr_t))) == NULL) + return (NULL); + + temp->op = op; + temp->invert = invert; + temp->key = (char *)key; + + if (regex) + { + int err = regcomp(&(temp->re), regex, REG_NOSUB | REG_EXTENDED); + + if (err) + { + char message[256]; /* Error message */ + + regerror(err, &(temp->re), message, sizeof(message)); + _cupsLangPrintf(stderr, _("ippfind: Bad regular expression: %s"), + message); + exit(IPPFIND_EXIT_SYNTAX); + } + } + + if (args) + { + int num_args; /* Number of arguments */ + + for (num_args = 1; args[num_args]; num_args ++) + if (!strcmp(args[num_args], ";")) + break; + + temp->args = malloc(num_args * sizeof(char *)); + memcpy(temp->args, args, num_args * sizeof(char *)); + } + + return (temp); +} + + #ifdef HAVE_AVAHI /* * 'poll_callback()' - Wait for input on the specified file descriptors. @@ -1045,7 +1738,12 @@ resolve_callback( */ if (errorCode != kDNSServiceErr_NoError) + { + _cupsLangPrintf(stderr, _("ippfind: Unable to browse or resolve: %s"), + dnssd_error_string(errorCode)); + bonjour_error = 1; return; + } service->is_resolved = 1; service->host = strdup(hostTarget); @@ -1198,10 +1896,10 @@ set_service_uri(ippfind_srv_t *service) /* I - Service */ path = "/"; if (*path == '/') - httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL, + httpAssembleURI(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, service->host, service->port, path); else - httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(URI), scheme, NULL, + httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), scheme, NULL, service->host, service->port, "/%s", path); service->uri = strdup(uri); @@ -1209,11 +1907,11 @@ set_service_uri(ippfind_srv_t *service) /* I - Service */ /* - * 'usage()' - Show program usage. + * 'show_usage()' - Show program usage. */ static void -usage(void) +show_usage(void) { _cupsLangPuts(stderr, _("Usage: ippfind [options] regtype[,subtype]" "[.domain.] ... [expression]\n" @@ -1237,33 +1935,63 @@ usage(void) _cupsLangPuts(stderr, _(" -e utility [argument ...] ;\n" " Execute program if true.")); _cupsLangPuts(stderr, _(" -l List attributes.")); - _cupsLangPuts(stderr, _(" --local True if service is local.")); _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" --path regex Match resource path to regular expression.")); _cupsLangPuts(stderr, _(" -p Print URI if true.")); _cupsLangPuts(stderr, _(" -q Quietly report match via exit code.")); _cupsLangPuts(stderr, _(" -r True if service is remote.")); - - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -n regex Match service name to regular expression.")); - _cupsLangPuts(stderr, _(" -d name=value Set named variable to " - "value.")); - _cupsLangPuts(stderr, _(" -f filename Set default request " - "filename.")); - _cupsLangPuts(stderr, _(" -i seconds Repeat the last file with " - "the given time interval.")); - _cupsLangPuts(stderr, _(" -n count Repeat the last file the " - "given number of times.")); - _cupsLangPuts(stderr, _(" -q Run silently.")); - _cupsLangPuts(stderr, _(" -t Produce a test report.")); - _cupsLangPuts(stderr, _(" -v Be verbose.")); - - exit(IPPFIND_EXIT_OK); + _cupsLangPuts(stderr, _(" -s Print service name if true.")); + _cupsLangPuts(stderr, _(" -t key True if the TXT record contains the key.")); + _cupsLangPuts(stderr, _(" -u regex Match URI to regular expression.")); + _cupsLangPuts(stderr, _(" --domain regex Match domain to regular expression.")); + _cupsLangPuts(stderr, _(" --exec utility [argument ...] ;\n" + " Execute program if true.")); + _cupsLangPuts(stderr, _(" --ls List attributes.")); + _cupsLangPuts(stderr, _(" --local True if service is local.")); + _cupsLangPuts(stderr, _(" --name regex Match service name to regular expression.")); + _cupsLangPuts(stderr, _(" --path regex Match resource path to regular expression.")); + _cupsLangPuts(stderr, _(" --print Print URI if true.")); + _cupsLangPuts(stderr, _(" --print-name Print service name if true.")); + _cupsLangPuts(stderr, _(" --quiet Quietly report match via exit code.")); + _cupsLangPuts(stderr, _(" --remote True if service is remote.")); + _cupsLangPuts(stderr, _(" --txt key True if the TXT record contains the key.")); + _cupsLangPuts(stderr, _(" --txt-* regex Match TXT record key to regular expression.")); + _cupsLangPuts(stderr, _(" --uri regex Match URI to regular expression.")); + _cupsLangPuts(stderr, ""); + _cupsLangPuts(stderr, _("Modifiers:")); + _cupsLangPuts(stderr, _(" ( expressions ) Group expressions.")); + _cupsLangPuts(stderr, _(" ! expression Unary NOT of expression.")); + _cupsLangPuts(stderr, _(" --not expression Unary NOT of expression.")); + _cupsLangPuts(stderr, _(" --false Always false.")); + _cupsLangPuts(stderr, _(" --true Always true.")); + _cupsLangPuts(stderr, _(" expression expression Logical AND.")); + _cupsLangPuts(stderr, _(" expression --and expression\n" + " Logical AND.")); + _cupsLangPuts(stderr, _(" expression --or expression\n" + " Logical OR.")); + _cupsLangPuts(stderr, ""); + _cupsLangPuts(stderr, _("Substitutions:")); + _cupsLangPuts(stderr, _(" {} URI")); + _cupsLangPuts(stderr, _(" {service_domain} Domain name")); + _cupsLangPuts(stderr, _(" {service_hostname} Fully-qualified domain name")); + _cupsLangPuts(stderr, _(" {service_name} Service instance name")); + _cupsLangPuts(stderr, _(" {service_port} Port number")); + _cupsLangPuts(stderr, _(" {service_regtype} DNS-SD registration type")); + _cupsLangPuts(stderr, _(" {service_scheme} URI scheme")); + _cupsLangPuts(stderr, _(" {service_uri} URI")); + _cupsLangPuts(stderr, _(" {txt_*} Value of TXT record key")); + _cupsLangPuts(stderr, ""); + _cupsLangPuts(stderr, _("Environment Variables:")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_DOMAIN Domain name")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_HOSTNAME\n" + " Fully-qualified domain name")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_NAME Service instance name")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_PORT Port number")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_REGTYPE DNS-SD registration type")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_SCHEME URI scheme")); + _cupsLangPuts(stderr, _(" IPPFIND_SERVICE_URI URI")); + _cupsLangPuts(stderr, _(" IPPFIND_TXT_* Value of TXT record key")); + + exit(IPPFIND_EXIT_TRUE); } @@ -1276,41 +2004,7 @@ show_version(void) { _cupsLangPuts(stderr, CUPS_SVERSION); - exit(IPPFIND_EXIT_OK); -} - - -/* - * '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'; + exit(IPPFIND_EXIT_TRUE); } diff --git a/test/run-stp-tests.sh b/test/run-stp-tests.sh index 18b7b9051..656503a5a 100755 --- a/test/run-stp-tests.sh +++ b/test/run-stp-tests.sh @@ -100,6 +100,7 @@ case "$testtype" in nprinters2=0 pjobs=0 pprinters=0 + loglevel="debug2" ;; 2) echo "Running the medium tests (2)" @@ -107,6 +108,7 @@ case "$testtype" in nprinters2=20 pjobs=20 pprinters=10 + loglevel="debug" ;; 3) echo "Running the extreme tests (3)" @@ -114,6 +116,7 @@ case "$testtype" in nprinters2=1000 pjobs=100 pprinters=50 + loglevel="debug" ;; 4) echo "Running the torture tests (4)" @@ -121,6 +124,7 @@ case "$testtype" in nprinters2=20000 pjobs=200 pprinters=100 + loglevel="debug" ;; *) echo "Running the timid tests (1)" @@ -128,6 +132,7 @@ case "$testtype" in nprinters2=0 pjobs=10 pprinters=0 + loglevel="debug2" ;; esac @@ -397,7 +402,7 @@ PassEnv DYLD_INSERT_LIBRARIES MaxSubscriptions 3 MaxLogSize 0 AccessLogLevel actions -LogLevel debug2 +LogLevel $loglevel LogTimeFormat usecs PreserveJobHistory Yes PreserveJobFiles No -- 2.39.2