//
// Serial port backend for cups-filters.
//
// Copyright 2007-2011 by Apple Inc.
// Copyright 1997-2007 by Easy Software Products, all rights reserved.
//
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
// information.
//
// Contents:
//
//   main()         - Send a file to the printer or server.
//   list_devices() - List all serial devices.
//   side_cb()      - Handle side-channel requests...
//

//
// Include necessary headers.
//

#include <config.h>
#include <cups/cups.h>
#include <cups/backend.h>
#include <cups/sidechannel.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <signal.h>
#include <sys/select.h>
#ifdef HAVE_SYS_IOCTL_H
#  include <sys/ioctl.h>
#endif // HAVE_SYS_IOCTL_H

#ifndef CRTSCTS
#  ifdef CNEW_RTSCTS
#    define CRTSCTS CNEW_RTSCTS
#  else
#    define CRTSCTS 0
#  endif // CNEW_RTSCTS
#endif // !CRTSCTS

#if defined(__APPLE__)
#  include <CoreFoundation/CoreFoundation.h>
#  include <IOKit/IOKitLib.h>
#  include <IOKit/serial/IOSerialKeys.h>
#  include <IOKit/IOBSD.h>
#endif // __APPLE__

#if defined(__linux) && defined(TIOCGSERIAL)
#  include <linux/serial.h>
#  include <linux/ioctl.h>
#endif // __linux && TIOCGSERIAL


//
// Local functions...
//

static int	drain_output(int print_fd, int device_fd);
static void	list_devices(void);
static int	side_cb(int print_fd, int device_fd, int use_bc);


//
// 'main()' - Send a file to the printer or server.
//
// Usage:
//
//    printer-uri job-id user title copies options [file]
//

int					// O - Exit status
main(int  argc,				// I - Number of command-line arguments (6 or 7)
     char *argv[])			// I - Command-line arguments
{
  char		method[255],		// Method in URI
		hostname[1024],		// Hostname
		username[255],		// Username info (not used)
		resource[1024],		// Resource info (device and options)
		*options,		// Pointer to options
		*name,			// Name of option
		*value,			// Value of option
		sep;			// Option separator
  int		port;			// Port number (not used)
  int		copies;			// Number of copies to print
  int		side_eof = 0,		// Saw EOF on side-channel?
		print_fd,		// Print file
		device_fd;		// Serial device
  int		nfds;			// Maximum file descriptor value + 1
  fd_set	input,			// Input set for reading
		output;			// Output set for writing
  ssize_t	print_bytes,		// Print bytes read
		bc_bytes,		// Backchannel bytes read
		total_bytes,		// Total bytes written
		bytes;			// Bytes written
  int		dtrdsr;			// Do dtr/dsr flow control?
  int		print_size;		// Size of output buffer for writes
  char		print_buffer[8192],	// Print data buffer
		*print_ptr,		// Pointer into print data buffer
		bc_buffer[1024];	// Back-channel data buffer
  struct termios opts;			// Serial port options
  struct termios origopts;		// Original port options
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
  struct sigaction action;		// Actions for POSIX signals
#endif // HAVE_SIGACTION && !HAVE_SIGSET
  char		print_sleep = 0;	// Print first sleep flag on every transmit


  //
  // Make sure status messages are not buffered...
  //

  setbuf(stderr, NULL);

  //
  // Ignore SIGPIPE signals...
  //

#ifdef HAVE_SIGSET
  sigset(SIGPIPE, SIG_IGN);
#elif defined(HAVE_SIGACTION)
  memset(&action, 0, sizeof(action));
  action.sa_handler = SIG_IGN;
  sigaction(SIGPIPE, &action, NULL);
#else
  signal(SIGPIPE, SIG_IGN);
#endif // HAVE_SIGSET

  //
  // Check command-line...
  //

  if (argc == 1)
  {
    list_devices();
    return (CUPS_BACKEND_OK);
  }
  else if (argc < 6 || argc > 7)
  {
    fprintf(stderr, "Usage: %s job-id user title copies options [file]\n",
	    argv[0]);
    return (CUPS_BACKEND_FAILED);
  }

  //
  // If we have 7 arguments, print the file named on the command-line.
  // Otherwise, send stdin instead...
  //

  if (argc == 6)
  {
    print_fd = 0;
    copies   = 1;
  }
  else
  {
    //
    // Try to open the print file...
    //

    if ((print_fd = open(argv[6], O_RDONLY)) < 0)
    {
      perror("ERROR: Unable to open print file");
      return (CUPS_BACKEND_FAILED);
    }

    copies = atoi(argv[4]);
  }

  //
  // Extract the device name and options from the URI...
  //

  httpSeparateURI(HTTP_URI_CODING_ALL, cupsBackendDeviceURI(argv),
                  method, sizeof(method), username, sizeof(username),
		  hostname, sizeof(hostname), &port,
		  resource, sizeof(resource));

  //
  // See if there are any options...
  //

  if ((options = strchr(resource, '?')) != NULL)
  {
    //
    // Yup, terminate the device name string and move to the first
    // character of the options...
    //

    *options++ = '\0';
  }

  //
  // Open the serial port device...
  //

  fputs("STATE: +connecting-to-device\n", stderr);

  do
  {
    if ((device_fd = open(resource, O_RDWR | O_NOCTTY | O_EXCL |
                                    O_NDELAY)) == -1)
    {
      if (getenv("CLASS") != NULL)
      {
	//
        // If the CLASS environment variable is set, the job was submitted
        // to a class and not to a specific queue.  In this case, we want
	// to abort immediately so that the job can be requeued on the next
	// available printer in the class.
	//

        fputs("INFO: Unable to contact printer, queuing on next printer in "
              "class.\n", stderr);

	//
        // Sleep 5 seconds to keep the job from requeuing too rapidly...
	//

	sleep(5);

        return (CUPS_BACKEND_FAILED);
      }

      if (errno == EBUSY)
      {
        fputs("INFO: Printer busy; will retry in 30 seconds.\n", stderr);
	sleep(30);
      }
      else
      {
	perror("ERROR: Unable to open serial port");
	return (CUPS_BACKEND_FAILED);
      }
    }
  }
  while (device_fd < 0);

  fputs("STATE: -connecting-to-device\n", stderr);

  //
  // Set any options provided...
  //

  tcgetattr(device_fd, &origopts);
  tcgetattr(device_fd, &opts);

  opts.c_lflag &= ~(ICANON | ECHO | ISIG);
					// Raw mode
  opts.c_oflag &= ~OPOST;		// Don't post-process

  print_size = 96;			// 9600 baud / 10 bits/char / 10Hz
  dtrdsr     = 0;			// No dtr/dsr flow control

  if (options)
  {
    while (*options)
    {
      //
      // Get the name...
      //

      name = options;

      while (*options && *options != '=' && *options != '+' && *options != '&')
        options ++;

      if ((sep = *options) != '\0')
        *options++ = '\0';

      if (sep == '=')
      {
	//
        // Get the value...
	//

        value = options;

	while (*options && *options != '+' && *options != '&')
	  options ++;

        if (*options)
	  *options++ = '\0';
      }
      else
        value = (char *)"";

      //
      // Process the option...
      //

      if (!strcasecmp(name, "baud"))
      {
	//
        // Set the baud rate...
	//

        print_size = atoi(value) / 100;

#if B19200 == 19200
        cfsetispeed(&opts, atoi(value));
	cfsetospeed(&opts, atoi(value));
#else
        switch (atoi(value))
	{
	  case 1200 :
	      cfsetispeed(&opts, B1200);
	      cfsetospeed(&opts, B1200);
	      break;
	  case 2400 :
	      cfsetispeed(&opts, B2400);
	      cfsetospeed(&opts, B2400);
	      break;
	  case 4800 :
	      cfsetispeed(&opts, B4800);
	      cfsetospeed(&opts, B4800);
	      break;
	  case 9600 :
	      cfsetispeed(&opts, B9600);
	      cfsetospeed(&opts, B9600);
	      break;
	  case 19200 :
	      cfsetispeed(&opts, B19200);
	      cfsetospeed(&opts, B19200);
	      break;
	  case 38400 :
	      cfsetispeed(&opts, B38400);
	      cfsetospeed(&opts, B38400);
	      break;
#  ifdef B57600
	  case 57600 :
	      cfsetispeed(&opts, B57600);
	      cfsetospeed(&opts, B57600);
	      break;
#  endif // B57600
#  ifdef B115200
	  case 115200 :
	      cfsetispeed(&opts, B115200);
	      cfsetospeed(&opts, B115200);
	      break;
#  endif // B115200
#  ifdef B230400
	  case 230400 :
	      cfsetispeed(&opts, B230400);
	      cfsetospeed(&opts, B230400);
	      break;
#  endif // B230400
          default :
	      fprintf(stderr, "WARNING: Unsupported baud rate: %s\n", value);
	      break;
	}
#endif // B19200 == 19200
      }
      else if (!strcasecmp(name, "bits"))
      {
	//
        // Set number of data bits...
	//

        switch (atoi(value))
	{
	  case 7 :
	      opts.c_cflag &= ~CSIZE;
              opts.c_cflag |= CS7;
	      opts.c_cflag |= PARENB;
              opts.c_cflag &= ~PARODD;
              break;
	  case 8 :
	      opts.c_cflag &= ~CSIZE;
              opts.c_cflag |= CS8;
	      opts.c_cflag &= ~PARENB;
	      break;
	}
      }
      else if (!strcasecmp(name, "parity"))
      {
	//
	// Set parity checking...
	//

	if (!strcasecmp(value, "even"))
	{
	  opts.c_cflag |= PARENB;
          opts.c_cflag &= ~PARODD;
	}
	else if (!strcasecmp(value, "odd"))
	{
	  opts.c_cflag |= PARENB;
          opts.c_cflag |= PARODD;
	}
	else if (!strcasecmp(value, "none"))
	  opts.c_cflag &= ~PARENB;
	else if (!strcasecmp(value, "space"))
	{
	  //
	  // Note: we only support space parity with 7 bits per character...
	  //

	  opts.c_cflag &= ~CSIZE;
          opts.c_cflag |= CS8;
	  opts.c_cflag &= ~PARENB;
        }
	else if (!strcasecmp(value, "mark"))
	{
	  //
	  // Note: we only support mark parity with 7 bits per character
	  // and 1 stop bit...
	  //

	  opts.c_cflag &= ~CSIZE;
          opts.c_cflag |= CS7;
	  opts.c_cflag &= ~PARENB;
          opts.c_cflag |= CSTOPB;
        }
      }
      else if (!strcasecmp(name, "flow"))
      {
	//
	// Set flow control...
	//

	if (!strcasecmp(value, "none"))
	{
	  opts.c_iflag &= ~(IXON | IXOFF);
          opts.c_cflag &= ~CRTSCTS;
	}
	else if (!strcasecmp(value, "soft"))
	{
	  opts.c_iflag |= IXON | IXOFF;
          opts.c_cflag &= ~CRTSCTS;
	}
	else if (!strcasecmp(value, "hard") ||
	         !strcasecmp(value, "rtscts"))
        {
	  opts.c_iflag &= ~(IXON | IXOFF);
          opts.c_cflag |= CRTSCTS;
	}
	else if (!strcasecmp(value, "dtrdsr"))
	{
	  opts.c_iflag &= ~(IXON | IXOFF);
          opts.c_cflag &= ~CRTSCTS;

	  dtrdsr = 1;
	}
      }
      else if (!strcasecmp(name, "stop"))
      {
        switch (atoi(value))
	{
	  case 1 :
	      opts.c_cflag &= ~CSTOPB;
	      break;

	  case 2 :
	      opts.c_cflag |= CSTOPB;
	      break;
	}
      }
    }
  }

  tcsetattr(device_fd, TCSANOW, &opts);
  fcntl(device_fd, F_SETFL, 0);

  //
  // Now that we are "connected" to the port, ignore SIGTERM so that we
  // can finish out any page data the driver sends (e.g. to eject the
  // current page...  Only ignore SIGTERM if we are printing data from
  // stdin (otherwise you can't cancel raw jobs...)
  //

  if (!print_fd)
  {
#ifdef HAVE_SIGSET // Use System V signals over POSIX to avoid bugs
    sigset(SIGTERM, SIG_IGN);
#elif defined(HAVE_SIGACTION)
    memset(&action, 0, sizeof(action));

    sigemptyset(&action.sa_mask);
    action.sa_handler = SIG_IGN;
    sigaction(SIGTERM, &action, NULL);
#else
    signal(SIGTERM, SIG_IGN);
#endif // HAVE_SIGSET
  }

  //
  // Figure out the maximum file descriptor value to use with select()...
  //

  nfds = (print_fd > device_fd ? print_fd : device_fd) + 1;

  //
  // Finally, send the print file.  Ordinarily we would just use the
  // backendRunLoop() function, however since we need to use smaller
  // writes and may need to do DSR/DTR flow control, we duplicate much
  // of the code here instead...
  //

  if (print_size > sizeof(print_buffer))
    print_size = sizeof(print_buffer);

  total_bytes = 0;

  while (copies > 0)
  {
    copies --;

    if (print_fd != 0)
    {
      fputs("PAGE: 1 1\n", stderr);
      lseek(print_fd, 0, SEEK_SET);
    }

    //
    // Now loop until we are out of data from print_fd...
    //

    for (print_bytes = 0, print_ptr = print_buffer;;)
    {
      //
      // Use select() to determine whether we have data to copy around...
      //

      FD_ZERO(&input);
      if (!print_bytes)
	FD_SET(print_fd, &input);
      FD_SET(device_fd, &input);
      if (!print_bytes && !side_eof)
        FD_SET(CUPS_SC_FD, &input);

      FD_ZERO(&output);
      if (print_bytes)
	FD_SET(device_fd, &output);

      if (select(nfds, &input, &output, NULL, NULL) < 0)
	continue;			// Ignore errors here

      //
      // Check if we have a side-channel request ready...
      //

      if (FD_ISSET(CUPS_SC_FD, &input))
      {
	//
	// Do the side-channel request, then start back over in the select
	// loop since it may have read from print_fd...
	//

        if (side_cb(print_fd, device_fd, 1))
	  side_eof = 1;
	continue;
      }

      //
      // Check if we have back-channel data ready...
      //

      if (FD_ISSET(device_fd, &input))
      {
	if ((bc_bytes = read(device_fd, bc_buffer, sizeof(bc_buffer))) > 0)
	{
	  fprintf(stderr, "DEBUG: Received %d bytes of back-channel data.\n",
	          (int)bc_bytes);
          cupsBackChannelWrite(bc_buffer, bc_bytes, 1.0);
	}
      }

      //
      // Check if we have print data ready...
      //

      if (FD_ISSET(print_fd, &input))
      {
	if ((print_bytes = read(print_fd, print_buffer, print_size)) < 0)
	{
	  //
          // Read error - bail if we don't see EAGAIN or EINTR...
	  //

	  if (errno != EAGAIN && errno != EINTR)
	  {
	    perror("DEBUG: Unable to read print data");

            tcsetattr(device_fd, TCSADRAIN, &origopts);

	    close(device_fd);

	    if (print_fd != 0)
	      close(print_fd);

	    return (CUPS_BACKEND_FAILED);
	  }

          print_bytes = 0;
	}
	else if (print_bytes == 0)
	{
	  //
          // End of file, break out of the loop...
	  //

          break;
	}

	print_ptr = print_buffer;
      }

      //
      // Check if the device is ready to receive data and we have data to
      // send...
      //

      if (print_bytes && FD_ISSET(device_fd, &output))
      {
	if (dtrdsr)
	{
	  //
	  // Check the port and sleep until DSR is set...
	  //

	  int status;


	  if (!ioctl(device_fd, TIOCMGET, &status))
            if (!(status & TIOCM_DSR))
	    {
	      //
	      // Wait for DSR to go high...
	      //

	      fputs("DEBUG: DSR is low; waiting for device.\n", stderr);

              do
	      {
		//
	        // Poll every 100ms...
		//

		usleep(100000);

		if (ioctl(device_fd, TIOCMGET, &status))
		  break;
	      }
	      while (!(status & TIOCM_DSR));

	      fputs("DEBUG: DSR is high; writing to device.\n", stderr);
            }
	}

	//
	// On every transmit need to wait a little
	// even though the DSR is OK for some unknown reasons.
	//

	if (print_sleep == 0)
	{
		usleep(10000);
		print_sleep = 1;
	}

	if ((bytes = write(device_fd, print_ptr, print_bytes)) < 0)
	{
	  //
          // Write error - bail if we don't see an error we can retry...
	  //

	  if (errno != EAGAIN && errno != EINTR && errno != ENOTTY)
	  {
	    perror("DEBUG: Unable to write print data");

            tcsetattr(device_fd, TCSADRAIN, &origopts);

	    close(device_fd);

	    if (print_fd != 0)
	      close(print_fd);

	    return (CUPS_BACKEND_FAILED);
	  }
	}
	else
	{
          tcdrain(device_fd);
          fprintf(stderr, "DEBUG: Wrote %d bytes.\n", (int)bytes);

          print_bytes -= bytes;
	  print_ptr   += bytes;
	  total_bytes += bytes;
	}
      }
    }
  }

  //
  // Close the serial port and input file and return...
  //

  tcsetattr(device_fd, TCSADRAIN, &origopts);

  close(device_fd);

  if (print_fd != 0)
    close(print_fd);

  return (CUPS_BACKEND_OK);
}


//
// 'drain_output()' - Drain pending print data to the device.
//

static int				// O - 0 on success, -1 on error
drain_output(int print_fd,		// I - Print file descriptor
             int device_fd)		// I - Device file descriptor
{
  int		nfds;			// Maximum file descriptor value + 1
  fd_set	input;			// Input set for reading
  ssize_t	print_bytes,		// Print bytes read
		bytes;			// Bytes written
  char		print_buffer[8192],	// Print data buffer
		*print_ptr;		// Pointer into print data buffer
  struct timeval timeout;		// Timeout for read...


 //
 // Figure out the maximum file descriptor value to use with select()...
 //

  nfds = (print_fd > device_fd ? print_fd : device_fd) + 1;

  //
  // Now loop until we are out of data from print_fd...
  //

  for (;;)
  {
    //
    // Use select() to determine whether we have data to copy around...
    //

    FD_ZERO(&input);
    FD_SET(print_fd, &input);

    timeout.tv_sec  = 0;
    timeout.tv_usec = 0;

    if (select(nfds, &input, NULL, NULL, &timeout) < 0)
      return (-1);

    if (!FD_ISSET(print_fd, &input))
      return (0);

    if ((print_bytes = read(print_fd, print_buffer,
			    sizeof(print_buffer))) < 0)
    {
      //
      // Read error - bail if we don't see EAGAIN or EINTR...
      //

      if (errno != EAGAIN && errno != EINTR)
      {
        perror("ERROR: Unable to read print data");
	return (-1);
      }

      print_bytes = 0;
    }
    else if (print_bytes == 0)
    {
      //
      // End of file, return...
      //

      return (0);
    }

    fprintf(stderr, "DEBUG: Read %d bytes of print data.\n",
	    (int)print_bytes);

    for (print_ptr = print_buffer; print_bytes > 0;)
    {
      if ((bytes = write(device_fd, print_ptr, print_bytes)) < 0)
      {
	//
        // Write error - bail if we don't see an error we can retry...
	//

        if (errno != ENOSPC && errno != ENXIO && errno != EAGAIN &&
	    errno != EINTR && errno != ENOTTY)
	{
	  perror("ERROR: Unable to write print data");
	  return (-1);
	}
      }
      else
      {
        fprintf(stderr, "DEBUG: Wrote %d bytes of print data.\n", (int)bytes);

        print_bytes -= bytes;
	print_ptr   += bytes;
      }
    }
  }
}


//
// 'list_devices()' - List all serial devices.
//

static void
list_devices(void)
{
#if defined(__sun) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
  static char	*funky_hex = "0123456789abcdefghijklmnopqrstuvwxyz";
					/* Funky hex numbering used for some *
					 * devices                           */
#endif // __sun || __FreeBSD__ || __OpenBSD__ || __FreeBSD_kernel__


#ifdef __linux
  int			i, j;		// Looping vars
  int			fd;		// File descriptor
  char			device[255];	// Device filename
  char			info[255];	// Device info/description
#  ifdef TIOCGSERIAL
  struct serial_struct	serinfo;	// serial port info
#  endif // TIOCGSERIAL


  for (i = 0; i < 100; i ++)
  {
    sprintf(device, "/dev/ttyS%d", i);

    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
#  ifdef TIOCGSERIAL
      //
      // See if this port exists...
      //

      serinfo.reserved_char[0] = 0;

      if (!ioctl(fd, TIOCGSERIAL, &serinfo))
      {
	if (serinfo.type == PORT_UNKNOWN)
	{
	  //
	  // Nope...
	  //

	  close(fd);
	  continue;
	}
      }
#  endif // TIOCGSERIAL

      close(fd);

      snprintf(info, sizeof(info), "Serial Port #%d", i + 1);

#  if defined(_ARCH_PPC) || defined(powerpc) || defined(__powerpc)
      printf("serial serial:%s?baud=230400 \"Unknown\" \"%s\"\n", device, info);
#  else
      printf("serial serial:%s?baud=115200 \"Unknown\" \"%s\"\n", device, info);
#  endif // _ARCH_PPC || powerpc || __powerpc
    }
  }

  for (i = 0; i < 16; i ++)
  {
    snprintf(info, sizeof(info), "USB Serial Port #%d", i + 1);

    sprintf(device, "/dev/usb/ttyUSB%d", i);
    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
      close(fd);
      printf("serial serial:%s?baud=230400 \"Unknown\" \"%s\"\n", device, info);
    }

    sprintf(device, "/dev/ttyUSB%d", i);
    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
      close(fd);
      printf("serial serial:%s?baud=230400 \"Unknown\" \"%s\"\n", device, info);
    }
  }

  for (i = 0; i < 64; i ++)
  {
    for (j = 0; j < 8; j ++)
    {
      sprintf(device, "/dev/ttyQ%02de%d", i, j);
      if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
      {
        close(fd);

        printf("serial serial:%s?baud=115200 \"Unknown\" "
	       "\"Equinox ESP %d Port #%d\"\n", device, i, j + 1);
      }
    }
  }
#elif defined(__sun)
  int		i, j, n;		// Looping vars
  char		device[255];		// Device filename
  char		info[255];		// Device info/description


  //
  // Standard serial ports...
  //

  for (i = 0; i < 26; i ++)
  {
    sprintf(device, "/dev/cua/%c", 'a' + i);
    if (!access(device, 0))
    {
      snprintf(info, sizeof(info), "Serial Port #%d", i + 1);

#  ifdef B115200
      printf("serial serial:%s?baud=115200 \"Unknown\" \"%s\"\n", device, info);
#  else
      printf("serial serial:%s?baud=38400 \"Unknown\" \"%s\"\n", device, info);
#  endif // B115200
    }
  }

  //
  // MAGMA serial ports...
  //

  for (i = 0; i < 40; i ++)
  {
    sprintf(device, "/dev/term/%02d", i);
    if (access(device, 0) == 0)
      printf("serial serial:%s?baud=38400 \"Unknown\" \"MAGMA Serial Board #%d Port #%d\"\n",
             device, (i / 10) + 1, (i % 10) + 1);
  }

  //
  // Central Data serial ports...
  //

  for (i = 0; i < 9; i ++)
    for (j = 0; j < 8; j ++)
      for (n = 0; n < 32; n ++)
      {
        if (i == 8)	// EtherLite
          sprintf(device, "/dev/sts/ttyN%d%c", j, funky_hex[n]);
        else
          sprintf(device, "/dev/sts/tty%c%d%c", i + 'C', j,
                  funky_hex[n]);

	if (access(device, 0) == 0)
	{
	  if (i == 8)
	    printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data EtherLite Serial Port, ID %d, port %d\"\n",
  	           device, j, n);
  	  else
	    printf("serial serial:%s?baud=38400 \"Unknown\" \"Central Data SCSI Serial Port, logical bus %d, ID %d, port %d\"\n",
  	           device, i, j, n);
	}
      }
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(__FreeBSD_kernel__)
  int	i, j;				// Looping vars
  int	fd;				// File descriptor
  char	device[255];			// Device filename
  char	info[255];			// Device info/description


  //
  // SIO ports...
  //

  for (i = 0; i < 32; i ++)
  {
    sprintf(device, "/dev/ttyd%c", funky_hex[i]);
    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
      close(fd);

      snprintf(info, sizeof(info), "Serial Port #%d", i + 1);

      printf("serial serial:%s?baud=115200 \"Unknown\" \"%s\"\n", device, info);
    }
  }

  //
  // Cyclades ports...
  //

  for (i = 0; i < 16; i ++) // Should be up to 65536 boards...
    for (j = 0; j < 32; j ++)
    {
      sprintf(device, "/dev/ttyc%d%c", i, funky_hex[j]);
      if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
      {
	close(fd);
	printf("serial serial:%s?baud=115200 \"Unknown\" \"Cyclades #%d Serial Port #%d\"\n",
               device, i, j + 1);
      }

      sprintf(device, "/dev/ttyC%d%c", i, funky_hex[j]);
      if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
      {
	close(fd);
	printf("serial serial:%s?baud=115200 \"Unknown\" \"Cyclades #%d Serial Port #%d\"\n",
               device, i, j + 1);
      }
    }

  //
  // Digiboard ports...
  //

  for (i = 0; i < 16; i ++) // Should be up to 65536 boards...
    for (j = 0; j < 32; j ++)
    {
      sprintf(device, "/dev/ttyD%d%c", i, funky_hex[j]);
      if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
      {
	close(fd);
	printf("serial serial:%s?baud=115200 \"Unknown\" \"Digiboard #%d Serial Port #%d\"\n",
               device, i, j + 1);
      }
    }

  //
  // Stallion ports...
  //

  for (i = 0; i < 32; i ++)
  {
    sprintf(device, "/dev/ttyE%c", funky_hex[i]);
    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
      close(fd);
      printf("serial serial:%s?baud=115200 \"Unknown\" \"Stallion Serial Port #%d\"\n",
             device, i + 1);
    }
  }

  //
  // SX ports...
  //

  for (i = 0; i < 128; i ++)
  {
    sprintf(device, "/dev/ttyA%d", i + 1);
    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
      close(fd);
      printf("serial serial:%s?baud=115200 \"Unknown\" \"SX Serial Port #%d\"\n",
             device, i + 1);
    }
  }
#elif defined(__NetBSD__)
  int	i, j;				// Looping vars
  int	fd;				// File descriptor
  char	device[255];			// Device filename
  char	info[255];			// Device info/description


  //
  // Standard serial ports...
  //

  for (i = 0; i < 4; i ++)
  {
    sprintf(device, "/dev/tty%02d", i);
    if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
    {
      close(fd);

      snprintf(info, sizeof(info), "Serial Port #%d", i + 1);

      printf("serial serial:%s?baud=115200 \"Unknown\" \"%s\"\n", device, info);
    }
  }

  //
  // Cyclades-Z ports...
  //

  for (i = 0; i < 16; i ++) // Should be up to 65536 boards...
    for (j = 0; j < 64; j ++)
    {
      sprintf(device, "/dev/ttyCZ%02d%02d", i, j);
      if ((fd = open(device, O_WRONLY | O_NOCTTY | O_NDELAY)) >= 0)
      {
	close(fd);
	printf("serial serial:%s?baud=115200 \"Unknown\" \"Cyclades #%d Serial Prt #%d\"\n",
	       device, i, j + 1);
      }
    }
#elif defined(__APPLE__)
  //
  // Standard serial ports on MacOS X...
  //

  kern_return_t			kernResult;
  mach_port_t			masterPort;
  io_iterator_t			serialPortIterator;
  CFMutableDictionaryRef	classesToMatch;
  io_object_t			serialService;


  kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);
  if (KERN_SUCCESS != kernResult)
    return;

  //
  // Serial devices are instances of class IOSerialBSDClient.
  //

  classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
  if (classesToMatch != NULL)
  {
    CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey),
                         CFSTR(kIOSerialBSDRS232Type));

    kernResult = IOServiceGetMatchingServices(masterPort, classesToMatch,
                                              &serialPortIterator);
    if (kernResult == KERN_SUCCESS)
    {
      while ((serialService = IOIteratorNext(serialPortIterator)))
      {
	CFTypeRef	serialNameAsCFString;
	CFTypeRef	bsdPathAsCFString;
	CFTypeRef	hiddenVal;
	char		serialName[128];
	char		bsdPath[1024];
	Boolean		result;


	// Check if hidden...
	hiddenVal = IORegistryEntrySearchCFProperty(serialService,
						    kIOServicePlane,
						    CFSTR("HiddenPort"),
						    kCFAllocatorDefault,
						    kIORegistryIterateRecursively |
						    kIORegistryIterateParents);
	if (hiddenVal)
	  CFRelease(hiddenVal);	// This interface should not be used
	else
	{
	  serialNameAsCFString =
	      IORegistryEntryCreateCFProperty(serialService,
					      CFSTR(kIOTTYDeviceKey),
					      kCFAllocatorDefault, 0);
	  if (serialNameAsCFString)
	  {
	    result = CFStringGetCString(serialNameAsCFString, serialName,
					sizeof(serialName),
					kCFStringEncodingASCII);
	    CFRelease(serialNameAsCFString);

	    if (result)
	    {
	      bsdPathAsCFString =
		  IORegistryEntryCreateCFProperty(serialService,
						  CFSTR(kIOCalloutDeviceKey),
						  kCFAllocatorDefault, 0);
	      if (bsdPathAsCFString)
	      {
		result = CFStringGetCString(bsdPathAsCFString, bsdPath,
					    sizeof(bsdPath),
					    kCFStringEncodingASCII);
		CFRelease(bsdPathAsCFString);

		if (result)
		  printf("serial serial:%s?baud=115200 \"Unknown\" \"%s\"\n",
			 bsdPath, serialName);
	      }
	    }
	  }
	}

	IOObjectRelease(serialService);
      }

      //
      // Release the iterator.
      //

      IOObjectRelease(serialPortIterator);
    }
  }
#endif
}


//
// 'side_cb()' - Handle side-channel requests...
//

static int				// O - 0 on success, -1 on error
side_cb(int print_fd,			// I - Print file
        int device_fd,			// I - Device file
	int use_bc)			// I - Using back-channel?
{
  cups_sc_command_t	command;	// Request command
  cups_sc_status_t	status;		// Request/response status
  char			data[2048];	// Request/response data
  int			datalen;	// Request/response data size


  datalen = sizeof(data);

  if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
    return (-1);

  switch (command)
  {
    case CUPS_SC_CMD_DRAIN_OUTPUT :
        if (drain_output(print_fd, device_fd))
	  status = CUPS_SC_STATUS_IO_ERROR;
	else if (tcdrain(device_fd))
	  status = CUPS_SC_STATUS_IO_ERROR;
	else
	  status = CUPS_SC_STATUS_OK;

	datalen = 0;
        break;

    case CUPS_SC_CMD_GET_BIDI :
	status  = CUPS_SC_STATUS_OK;
        data[0] = use_bc;
        datalen = 1;
        break;

    default :
        status  = CUPS_SC_STATUS_NOT_IMPLEMENTED;
	datalen = 0;
	break;
  }

  return (cupsSideChannelWrite(command, status, data, datalen, 1.0));
}
