<commit_before>/*
 * "$Id$"
 *
 *   Utility to find IPP printers via Bonjour/DNS-SD and optionally run
 *   commands such as IPP and Bonjour conformance tests.  This tool is
 *   inspired by the UNIX "find" command, thus its name.
 *
 *   Copyright 2008-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/".
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 */

/*
 * Include necessary headers.
 */

#include <cups/cups-private.h>
#include <regex.h>
#ifdef HAVE_DNSSD
#  include <dns_sd.h>
#elif defined(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_DNSSD */


/*
 * Structures...
 */

typedef enum ippfind_exit_e		/* Exit codes */
{
  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_MEMORY			/* Out of memory */
} ippfind_exit_t;

typedef enum ippfind_op_e		/* Operations for expressions */
{
  /* "Evaluation" operations */
  IPPFIND_OP_NONE,			/* No operation */
  IPPFIND_OP_AND,			/* Logical AND of all children */
  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_HOST_REGEX,		/* Hostname matches regular expression */
  IPPFIND_OP_PORT_RANGE,		/* Port matches range */
  IPPFIND_OP_PATH_REGEX,		/* Path matches regular expression */
  IPPFIND_OP_TXT_EXISTS,		/* TXT record key exists */
  IPPFIND_OP_TXT_REGEX,			/* TXT record key matches regular expression */
  IPPFIND_OP_URI_REGEX,			/* URI matches regular expression */

  /* "Output" operations */
  IPPFIND_OP_EXEC,			/* Execute when true */
  IPPFIND_OP_LIST,			/* List when true */
  IPPFIND_OP_PRINT_NAME,		/* Print URI when true */
  IPPFIND_OP_PRINT_URI,			/* Print name when true */
  IPPFIND_OP_QUIET,			/* No output when true */
} ippfind_op_t;

typedef struct ippfind_expr_s		/* Expression */
{
  struct ippfind_expr_s
		*prev,			/* Previous expression */
		*next,			/* Next expression */
		*parent,		/* Parent expressions */
		*child;			/* Child expressions */
  ippfind_op_t	op;			/* Operation code (see above) */
  int		invert;			/* Invert the result */
  char		*key;			/* TXT record key */
  regex_t	re;			/* Regular expression for matching */
  int		range[2];		/* Port number range */
  int		num_args;		/* Number of arguments for exec */
  char		**args;			/* Arguments for exec */
} ippfind_expr_t;

typedef struct ippfind_srv_s		/* Service information */
{
#ifdef HAVE_DNSSD
  DNSServiceRef	ref;			/* Service reference for query */
#elif defined(HAVE_AVAHI)
  AvahiServiceResolver *ref;		/* Resolver */
#endif /* HAVE_DNSSD */
  char		*name,			/* Service name */
		*domain,		/* Domain name */
		*regtype,		/* Registration type */
		*fullName,		/* Full name */
		*host,			/* Hostname */
		*resource,		/* Resource path */
		*uri;			/* URI */
  int		num_txt;		/* Number of TXT record keys */
  cups_option_t	*txt;			/* TXT record keys */
  int		port,			/* Port number */
		is_local,		/* Is a local service? */
		is_processed,		/* Did we process the service? */
		is_resolved;		/* Got the resolve data? */
} ippfind_srv_t;


/*
 * Local globals...
 */

#ifdef HAVE_DNSSD
static DNSServiceRef dnssd_ref;		/* Master service reference */
#elif defined(HAVE_AVAHI)
static AvahiClient *avahi_client = NULL;/* Client information */
static int	avahi_got_data = 0;	/* Got data from poll? */
static AvahiSimplePoll *avahi_poll = NULL;
					/* Poll information */
#endif /* HAVE_DNSSD */

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 */


/*
 * Local functions...
 */

#ifdef HAVE_DNSSD
static void		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		browse_local_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)));
#elif defined(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_callback(AvahiClient *client,
					AvahiClientState state,
					void *context);
#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 int		exec_program(ippfind_srv_t *service, int num_args,
			             char **args);
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 int		list_service(ippfind_srv_t *service);
static ippfind_expr_t	*new_expr(ippfind_op_t op, int invert,
			          const char *value, const char *regex,
			          char **args);
#ifdef HAVE_DNSSD
static void		resolve_callback(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)
				         __attribute__((nonnull(1,5,6,9, 10)));
#elif defined(HAVE_AVAHI)
static int		poll_callback(struct pollfd *pollfds,
			              unsigned int num_pollfds, int timeout,
			              void *context);
static void		resolve_callback(AvahiServiceResolver *res,
					 AvahiIfIndex interface,
					 AvahiProtocol protocol,
					 AvahiBrowserEvent event,
					 const char *serviceName,
					 const char *regtype,
					 const char *replyDomain,
					 const char *host_name,
					 uint16_t port,
					 AvahiStringList *txt,
					 AvahiLookupResultFlags flags,
					 void *context);
#endif /* HAVE_DNSSD */
static void		set_service_uri(ippfind_srv_t *service);
static void		show_usage(void) __attribute__((noreturn));
static void		show_version(void) __attribute__((noreturn));


/*
 * 'main()' - Browse for printers.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  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 */


 /*
  * Initialize the locale...
  */

  _cupsSetLocale(argv);

 /*
  * 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);

 /*
  * Parse command-line...
  */

  for (i = 1; i < argc; i ++)
  {
    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;

	  logic = IPPFIND_OP_AND;
	  temp  = NULL;
        }
        else if (!strcmp(argv[i], "--domain"))
        {
          i ++;
          if (i >= argc)
            show_usage();

          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 ((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;
        }
        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], "--host"))
        {
          i ++;
          if (i >= argc)
            show_usage();

          if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i],
                               NULL)) == NULL)
            return (IPPFIND_EXIT_MEMORY);
        }
        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], "--port"))
        {
          i ++;
          if (i >= argc)
            show_usage();

          if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL,
                               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 'P' :
		i ++;
		if (i >= argc)
		  show_usage();

		if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i],
		                     NULL, NULL)) == NULL)
		  return (IPPFIND_EXIT_MEMORY);
		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 'h' :
		i ++;
		if (i >= argc)
		  show_usage();

		if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL,
		                     argv[i], NULL)) == NULL)
		  return (IPPFIND_EXIT_MEMORY);
                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], "("))
    {
      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();
      }

      current = parent;
      parent  = current->parent;

      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]]
      */

      cupsArrayAdd(searches, argv[i]);
    }
  }

  if (parent)
  {
    _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis."));
    show_usage();
  }

  if (cupsArrayCount(searches) == 0)
  {
   /*
    * Add an implicit browse for IPP printers ("_ipp._tcp")...
    */

    cupsArrayAdd(searches, "_ipp._tcp");
  }

  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;

    temp->prev = current;
  }

 /*
  * Start up browsing/resolving...
  */

#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);
  }

#elif defined(HAVE_AVAHI)
  if ((avahi_poll = avahi_simple_poll_new()) == NULL)
  {
    _cupsLangPrintError(stderr, _("ippfind: Unable to use Bonjour: %s"),
                        strerror(errno));
    return (IPPFIND_EXIT_BONJOUR);
  }

  avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL);
<commit_msg>Fix --or handling.<commit_after>/*
 * "$Id$"
 *
 *   Utility to find IPP printers via Bonjour/DNS-SD and optionally run
 *   commands such as IPP and Bonjour conformance tests.  This tool is
 *   inspired by the UNIX "find" command, thus its name.
 *
 *   Copyright 2008-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/".
 *
 *   This file is subject to the Apple OS-Developed Software exception.
 *
 * Contents:
 *
 */

/*
 * Include necessary headers.
 */

#include <cups/cups-private.h>
#include <regex.h>
#ifdef HAVE_DNSSD
#  include <dns_sd.h>
#elif defined(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_DNSSD */


/*
 * Structures...
 */

typedef enum ippfind_exit_e		/* Exit codes */
{
  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_MEMORY			/* Out of memory */
} ippfind_exit_t;

typedef enum ippfind_op_e		/* Operations for expressions */
{
  /* "Evaluation" operations */
  IPPFIND_OP_NONE,			/* No operation */
  IPPFIND_OP_AND,			/* Logical AND of all children */
  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_HOST_REGEX,		/* Hostname matches regular expression */
  IPPFIND_OP_PORT_RANGE,		/* Port matches range */
  IPPFIND_OP_PATH_REGEX,		/* Path matches regular expression */
  IPPFIND_OP_TXT_EXISTS,		/* TXT record key exists */
  IPPFIND_OP_TXT_REGEX,			/* TXT record key matches regular expression */
  IPPFIND_OP_URI_REGEX,			/* URI matches regular expression */

  /* "Output" operations */
  IPPFIND_OP_EXEC,			/* Execute when true */
  IPPFIND_OP_LIST,			/* List when true */
  IPPFIND_OP_PRINT_NAME,		/* Print URI when true */
  IPPFIND_OP_PRINT_URI,			/* Print name when true */
  IPPFIND_OP_QUIET			/* No output when true */
} ippfind_op_t;

typedef struct ippfind_expr_s		/* Expression */
{
  struct ippfind_expr_s
		*prev,			/* Previous expression */
		*next,			/* Next expression */
		*parent,		/* Parent expressions */
		*child;			/* Child expressions */
  ippfind_op_t	op;			/* Operation code (see above) */
  int		invert;			/* Invert the result */
  char		*key;			/* TXT record key */
  regex_t	re;			/* Regular expression for matching */
  int		range[2];		/* Port number range */
  int		num_args;		/* Number of arguments for exec */
  char		**args;			/* Arguments for exec */
} ippfind_expr_t;

typedef struct ippfind_srv_s		/* Service information */
{
#ifdef HAVE_DNSSD
  DNSServiceRef	ref;			/* Service reference for query */
#elif defined(HAVE_AVAHI)
  AvahiServiceResolver *ref;		/* Resolver */
#endif /* HAVE_DNSSD */
  char		*name,			/* Service name */
		*domain,		/* Domain name */
		*regtype,		/* Registration type */
		*fullName,		/* Full name */
		*host,			/* Hostname */
		*resource,		/* Resource path */
		*uri;			/* URI */
  int		num_txt;		/* Number of TXT record keys */
  cups_option_t	*txt;			/* TXT record keys */
  int		port,			/* Port number */
		is_local,		/* Is a local service? */
		is_processed,		/* Did we process the service? */
		is_resolved;		/* Got the resolve data? */
} ippfind_srv_t;


/*
 * Local globals...
 */

#ifdef HAVE_DNSSD
static DNSServiceRef dnssd_ref;		/* Master service reference */
#elif defined(HAVE_AVAHI)
static AvahiClient *avahi_client = NULL;/* Client information */
static int	avahi_got_data = 0;	/* Got data from poll? */
static AvahiSimplePoll *avahi_poll = NULL;
					/* Poll information */
#endif /* HAVE_DNSSD */

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 */


/*
 * Local functions...
 */

#ifdef HAVE_DNSSD
static void		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		browse_local_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)));
#elif defined(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_callback(AvahiClient *client,
					AvahiClientState state,
					void *context);
#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 int		exec_program(ippfind_srv_t *service, int num_args,
			             char **args);
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 int		list_service(ippfind_srv_t *service);
static ippfind_expr_t	*new_expr(ippfind_op_t op, int invert,
			          const char *value, const char *regex,
			          char **args);
#ifdef HAVE_DNSSD
static void		resolve_callback(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)
				         __attribute__((nonnull(1,5,6,9, 10)));
#elif defined(HAVE_AVAHI)
static int		poll_callback(struct pollfd *pollfds,
			              unsigned int num_pollfds, int timeout,
			              void *context);
static void		resolve_callback(AvahiServiceResolver *res,
					 AvahiIfIndex interface,
					 AvahiProtocol protocol,
					 AvahiBrowserEvent event,
					 const char *serviceName,
					 const char *regtype,
					 const char *replyDomain,
					 const char *host_name,
					 uint16_t port,
					 AvahiStringList *txt,
					 AvahiLookupResultFlags flags,
					 void *context);
#endif /* HAVE_DNSSD */
static void		set_service_uri(ippfind_srv_t *service);
static void		show_usage(void) __attribute__((noreturn));
static void		show_version(void) __attribute__((noreturn));


/*
 * 'main()' - Browse for printers.
 */

int					/* O - Exit status */
main(int  argc,				/* I - Number of command-line args */
     char *argv[])			/* I - Command-line arguments */
{
  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 */
			*parens[100];	/* Markers for parenthesis */
  int			num_parens = 0;	/* Number of parenthesis */
  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 */


 /*
  * Initialize the locale...
  */

  _cupsSetLocale(argv);

 /*
  * 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);

 /*
  * Parse command-line...
  */

  for (i = 1; i < argc; i ++)
  {
    if (argv[i][0] == '-')
    {
      if (argv[i][1] == '-')
      {
       /*
        * Parse --option options...
        */

        if (!strcmp(argv[i], "--and"))
        {
          if (logic == IPPFIND_OP_OR)
          {
            _cupsLangPuts(stderr, _("ippfind: Cannot use --and after --or."));
            show_usage();
          }

          if (!current)
          {
            _cupsLangPuts(stderr,
                          _("ippfind: Missing expression before \"--and\"."));
            show_usage();
          }

	  temp = NULL;
        }
        else if (!strcmp(argv[i], "--domain"))
        {
          i ++;
          if (i >= argc)
            show_usage();

          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 ((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;
        }
        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], "--host"))
        {
          i ++;
          if (i >= argc)
            show_usage();

          if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL, argv[i],
                               NULL)) == NULL)
            return (IPPFIND_EXIT_MEMORY);
        }
        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 (!current)
          {
            _cupsLangPuts(stderr,
                          _("ippfind: Missing expression before \"--or\"."));
            show_usage();
          }

          logic = IPPFIND_OP_OR;

          if (parent && parent->op == IPPFIND_OP_OR)
          {
           /*
            * Already setup to do "foo --or bar --or baz"...
            */

            temp = NULL;
          }
          else if (!current->prev && parent)
          {
           /*
            * Change parent node into an OR node...
            */

            parent->op = IPPFIND_OP_OR;
            temp       = NULL;
          }
          else if (!current->prev)
          {
           /*
            * Need to group "current" in a new OR node...
            */

	    if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
				 NULL)) == NULL)
	      return (IPPFIND_EXIT_MEMORY);

            temp->parent    = parent;
            temp->child     = current;
            current->parent = temp;

            if (parent)
              parent->child = temp;
            else
              expressions = temp;

	    parent = temp;
	    temp   = NULL;
	  }
	  else
	  {
	   /*
	    * Need to group previous expressions in an AND node, and then
	    * put that in an OR node...
	    */

	    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;
	    current         = temp;

	    if ((temp = new_expr(IPPFIND_OP_OR, 0, NULL, NULL,
				 NULL)) == NULL)
	      return (IPPFIND_EXIT_MEMORY);

            temp->parent    = parent;
            current->parent = temp;

            if (parent)
              parent->child = temp;
            else
              expressions = temp;

	    parent = temp;
	    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], "--port"))
        {
          i ++;
          if (i >= argc)
            show_usage();

          if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i], NULL,
                               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 (logic == IPPFIND_OP_AND &&
	      current && current->prev &&
	      parent && parent->op != IPPFIND_OP_AND)
          {
           /*
            * Need to re-group "current" in a new AND node...
            */

            ippfind_expr_t *tempand;	/* Temporary AND node */

	    if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
				    NULL)) == NULL)
	      return (IPPFIND_EXIT_MEMORY);

           /*
            * Replace "current" with new AND node at the end of this list...
            */

            current->prev->next = tempand;
            tempand->prev       = current->prev;
            tempand->parent     = parent;

           /*
            * Add "current to the new AND node...
            */

            tempand->child  = current;
            current->parent = tempand;
            current->prev   = NULL;
	    parent          = tempand;
	  }

         /*
          * Add the new node at current level...
          */

	  if (current)
	  {
	    temp->parent  = parent;
	    current->next = temp;
	  }
	  else if (parent)
	    parent->child = temp;
	  else
	    expressions = temp;

	  temp->prev = current;
	  current    = temp;
          invert     = 0;
          logic      = IPPFIND_OP_AND;
          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 'P' :
		i ++;
		if (i >= argc)
		  show_usage();

		if ((temp = new_expr(IPPFIND_OP_PORT_RANGE, invert, argv[i],
		                     NULL, NULL)) == NULL)
		  return (IPPFIND_EXIT_MEMORY);
		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 'h' :
		i ++;
		if (i >= argc)
		  show_usage();

		if ((temp = new_expr(IPPFIND_OP_HOST_REGEX, invert, NULL,
		                     argv[i], NULL)) == NULL)
		  return (IPPFIND_EXIT_MEMORY);
                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 (logic == IPPFIND_OP_AND &&
	        current && current->prev &&
	        parent && parent->op != IPPFIND_OP_AND)
	    {
	     /*
	      * Need to re-group "current" in a new AND node...
	      */

	      ippfind_expr_t *tempand;	/* Temporary AND node */

	      if ((tempand = new_expr(IPPFIND_OP_AND, 0, NULL, NULL,
				      NULL)) == NULL)
		return (IPPFIND_EXIT_MEMORY);

	     /*
	      * Replace "current" with new AND node at the end of this list...
	      */

	      current->prev->next = tempand;
	      tempand->prev       = current->prev;
	      tempand->parent     = parent;

	     /*
	      * Add "current to the new AND node...
	      */

	      tempand->child  = current;
	      current->parent = tempand;
	      current->prev   = NULL;
	      parent          = tempand;
	    }

	   /*
	    * Add the new node at current level...
	    */

	    if (current)
	    {
	      temp->parent  = parent;
	      current->next = temp;
	    }
	    else if (parent)
	      parent->child = temp;
	    else
	      expressions = temp;

	    temp->prev = current;
	    current    = temp;
	    invert     = 0;
	    logic      = IPPFIND_OP_AND;
	    temp       = NULL;
	  }
        }
      }
    }
    else if (!strcmp(argv[i], "("))
    {
      if (num_parens >= 100)
      {
        _cupsLangPuts(stderr, _("ippfind: Too many parenthesis."));
        show_usage();
      }

      if ((temp = new_expr(IPPFIND_OP_AND, invert, NULL, NULL, NULL)) == NULL)
	return (IPPFIND_EXIT_MEMORY);

      parens[num_parens++] = temp;

      if (current)
      {
	temp->parent  = current->parent;
	current->next = temp;
	temp->prev    = current;
      }
      else
	expressions = temp;

      parent  = temp;
      current = NULL;
      invert  = 0;
      logic   = IPPFIND_OP_AND;
    }
    else if (!strcmp(argv[i], ")"))
    {
      if (num_parens <= 0)
      {
        _cupsLangPuts(stderr, _("ippfind: Missing open parenthesis."));
        show_usage();
      }

      current = parens[--num_parens];
      parent  = current->parent;
      invert  = 0;
      logic   = IPPFIND_OP_AND;
    }
    else if (!strcmp(argv[i], "!"))
    {
      invert = 1;
    }
    else
    {
     /*
      * _regtype._tcp[,subtype][.domain]
      *
      *   OR
      *
      * service-name[._regtype._tcp[.domain]]
      */

      cupsArrayAdd(searches, argv[i]);
    }
  }

  if (num_parens > 0)
  {
    _cupsLangPuts(stderr, _("ippfind: Missing close parenthesis."));
    show_usage();
  }

  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)
    {
      while (current->parent)
	current = current->parent;

      current->next = temp;
      temp->prev    = current;
    }
    else
      expressions = temp;
  }

  if (cupsArrayCount(searches) == 0)
  {
   /*
    * Add an implicit browse for IPP printers ("_ipp._tcp")...
    */

    cupsArrayAdd(searches, "_ipp._tcp");
  }

  if (getenv("IPPFIND_DEBUG"))
  {
    int			indent = 4;	/* Indentation */
    static const char * const ops[] =
    {
      "NONE",
      "AND",
      "OR",
      "TRUE",
      "FALSE",
      "IS_LOCAL",
      "IS_REMOTE",
      "DOMAIN_REGEX",
      "NAME_REGEX",
      "HOST_REGEX",
      "PORT_RANGE",
      "PATH_REGEX",
      "TXT_EXISTS",
      "TXT_REGEX",
      "URI_REGEX",
      "EXEC",
      "LIST",
      "PRINT_NAME",
      "PRINT_URI",
      "QUIET"
    };

    puts("Expression tree:");
    current = expressions;
    while (current)
    {
     /*
      * Print the current node...
      */

      printf("%*s%s%s\n", indent, "", current->invert ? "!" : "",
             ops[current->op]);

     /*
      * Advance to the next node...
      */

      if (current->child)
      {
        current = current->child;
        indent += 4;
      }
      else if (current->next)
        current = current->next;
      else if (current->parent)
      {
        current = current->parent->next;
        indent -= 4;
      }
      else
        current = NULL;
    }

    puts("\nSearch items:");
    for (search = (const char *)cupsArrayFirst(searches);
	 search;
	 search = (const char *)cupsArrayNext(searches))
      printf("    %s\n", search);
  }

 /*
  * Start up browsing/resolving...
  */

#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);
  }

#elif defined(HAVE_AVAHI)
  if ((avahi_poll = avahi_simple_poll_new()) == NULL)
  {
    _cupsLangPrintError(stderr, _("ippfind: Unable to use Bonjour: %s"),
                        strerror(errno));
    return (IPPFIND_EXIT_BONJOUR);
  }

  avahi_simple_poll_set_func(avahi_poll, poll_callback, NULL);

{'fraction_non_alphanumeric': 0.12543882013767677, 'fraction_numerical': 0.004020475004412717, 'mean_word_length': 1.7334619920660448, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 167, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5473246', 'n_tokens_mistral': 18131, 'n_tokens_neox': 16501, 'n_words': 3983}