]> git.ipfire.org Git - thirdparty/cups.git/blobdiff - backend/usb-darwin.c
Merge changes from CUPS 1.4svn-r7874.
[thirdparty/cups.git] / backend / usb-darwin.c
index d92591455d6904a14f183c2699d46c1b70eab7a2..d333795646e53b83f568069ed94eedb0b7610ae3 100644 (file)
@@ -1,7 +1,7 @@
 /*
-* "$Id: usb-darwin.c 6993 2007-09-28 18:05:28Z mike $"
+* "$Id: usb-darwin.c 7460 2008-04-16 02:19:54Z mike $"
 *
-* Copyright � 2005-2007 Apple Inc. All rights reserved.
+* Copyright 2005-2008 Apple Inc. All rights reserved.
 *
 * IMPORTANT:  This Apple software is supplied to you by Apple Computer,
 * Inc. ("Apple") in consideration of your agreement to the following
@@ -65,7 +65,7 @@
 *  cfstr_create_trim() - Create CFString and trim whitespace characters.
 *  parse_options()     - Parse uri options.
 *  setup_cfLanguage()  - Create AppleLanguages array from LANG environment var.
-*  run_ppc_backend()   - Re-exec i386 backend as ppc.
+*  run_legacy_backend()        - Re-exec backend as ppc or i386.
 *  sigterm_handler()   - SIGTERM handler.
 *  next_line()         - Find the next line in a buffer.
 *  parse_pserror()     - Scan the backchannel data for postscript errors.
@@ -83,6 +83,7 @@
 #include <fcntl.h>
 #include <termios.h>
 #include <unistd.h>
+#include <sys/stat.h>
 #include <sys/sysctl.h>
 #include <libgen.h>
 #include <mach/mach.h>
 #include <cups/debug.h>
 #include <cups/sidechannel.h>
 #include <cups/i18n.h>
+#include "backend-private.h"
 
 #include <CoreFoundation/CoreFoundation.h>
 #include <IOKit/usb/IOUSBLib.h>
 #include <IOKit/IOCFPlugIn.h>
 
+#include <spawn.h>
 #include <pthread.h>
 
+extern char **environ;
+
 
 /*
  * WAIT_EOF_DELAY is number of seconds we'll wait for responses from
@@ -273,11 +278,11 @@ static void setup_cfLanguage(void);
 static void soft_reset();
 static void status_timer_cb(CFRunLoopTimerRef timer, void *info);
 
-#if defined(__i386__)
+#if defined(__i386__) || defined(__x86_64__)
 static pid_t   child_pid;                                      /* Child PID */
-static void run_ppc_backend(int argc, char *argv[], int fd);   /* Starts child backend process running as a ppc executable */
+static void run_legacy_backend(int argc, char *argv[], int fd);        /* Starts child backend process running as a ppc executable */
 static void sigterm_handler(int sig);                          /* SIGTERM handler */
-#endif /* __i386__ */
+#endif /* __i386__ || __x86_64__ */
 
 #ifdef PARSE_PS_ERRORS
 static const char *next_line (const char *buffer);
@@ -314,7 +319,8 @@ print_device(const char *uri,               /* I - Device URI */
   OSStatus       status;               /* Function results */
   pthread_t      read_thread_id,       /* Read thread */
                  sidechannel_thread_id;/* Side-channel thread */
-  int            sidechannel_started = 0;/* Was the side-channel thread started? */
+  int            have_sidechannel = 0; /* Was the side-channel thread started? */
+  struct stat     sidechannel_info;    /* Side-channel file descriptor info */
   char           print_buffer[8192],   /* Print data buffer */
                  *print_ptr;           /* Pointer into print data buffer */
   UInt32         location;             /* Unique location in bus topology */
@@ -329,6 +335,17 @@ print_device(const char *uri,              /* I - Device URI */
   struct timespec cond_timeout;                /* pthread condition timeout */
 
 
+ /*
+  * See if the side-channel descriptor is valid...
+  */
+
+  have_sidechannel = !fstat(CUPS_SC_FD, &sidechannel_info) &&
+                     S_ISSOCK(sidechannel_info.st_mode);
+
+ /*
+  * Localize using CoreFoundation...
+  */
+
   setup_cfLanguage();
 
   parse_options(options, serial, sizeof(serial), &location, &g.wait_eof);
@@ -344,9 +361,14 @@ print_device(const char *uri,              /* I - Device URI */
 
   if (!g.make || !g.model)
   {
-    _cupsLangPrintf(stderr, 
-                   _("ERROR: Unable to create make and model strings\n"));
-    return CUPS_BACKEND_STOP;
+    _cupsLangPuts(stderr, _("ERROR: Fatal USB error!\n"));
+
+    if (!g.make)
+      fputs("DEBUG: USB make string is NULL!\n", stderr);
+    if (!g.model)
+      fputs("DEBUG: USB model string is NULL!\n", stderr);
+
+    return (CUPS_BACKEND_STOP);
   }
 
   fputs("STATE: +connecting-to-device\n", stderr);
@@ -373,17 +395,18 @@ print_device(const char *uri,             /* I - Device URI */
 
     status = registry_open(&driverBundlePath);
 
-#if defined(__i386__)
+#if defined(__i386__) || defined(__x86_64__)
     /*
-     * If we were unable to load the class drivers for this printer it's probably because they're ppc-only.
-     * In this case try to fork & exec this backend as a ppc executable so we can use them...
+     * If we were unable to load the class drivers for this printer it's
+     * probably because they're ppc or i386. In this case try to run this
+     * backend as i386 or ppc executables so we can use them...
      */
     if (status == -2)
     {
-      run_ppc_backend(argc, argv, print_fd);
+      run_legacy_backend(argc, argv, print_fd);
       /* Never returns here */
     }
-#endif /* __i386__ */
+#endif /* __i386__ || __x86_64__ */
 
     if (status ==  -2)
     {
@@ -396,12 +419,13 @@ print_device(const char *uri,             /* I - Device URI */
         strlcpy(print_buffer, "USB class driver", sizeof(print_buffer));
 
       fputs("STATE: +apple-missing-usbclassdriver-error\n", stderr);
-      _cupsLangPrintf(stderr, _("FATAL: Could not load %s\n"), print_buffer);
+      _cupsLangPuts(stderr, _("ERROR: Fatal USB error!\n"));
+      fprintf(stderr, "DEBUG: Could not load %s\n", print_buffer);
 
       if (driverBundlePath)
        CFRelease(driverBundlePath);
 
-      return CUPS_BACKEND_STOP;
+      return (CUPS_BACKEND_STOP);
     }
 
     if (driverBundlePath)
@@ -413,8 +437,9 @@ print_device(const char *uri,               /* I - Device URI */
       countdown -= PRINTER_POLLING_INTERVAL;
       if (countdown <= 0)
       {
-       _cupsLangPrintf(stderr, _("INFO: Printer busy (status:0x%08x)\n"),
-                       (int)status);
+       _cupsLangPuts(stderr,
+                     _("INFO: Waiting for printer to become available...\n"));
+       fprintf(stderr, "DEBUG: USB printer status: 0x%08x\n", (int)status);
        countdown = SUBSEQUENT_LOG_INTERVAL;    /* subsequent log entries, every 15 seconds */
       }
     }
@@ -442,21 +467,14 @@ print_device(const char *uri,             /* I - Device URI */
   }
 
  /*
-  * Start the side channel thread only if the descriptor is valid
-  * (i.e. it's not when the backend is used for auto-setup)...
+  * Start the side channel thread if the descriptor is valid...
   */
 
   pthread_mutex_init(&g.readwrite_lock_mutex, NULL);
   pthread_cond_init(&g.readwrite_lock_cond, NULL);
   g.readwrite_lock = 1;
 
-  FD_ZERO(&input_set);
-  FD_SET(CUPS_SC_FD, &input_set);
-
-  stimeout.tv_sec  = 0;
-  stimeout.tv_usec = 0;
-
-  if ((select(CUPS_SC_FD+1, &input_set, NULL, NULL, &stimeout)) >= 0)
+  if (have_sidechannel)
   {
     g.sidechannel_thread_stop = 0;
     g.sidechannel_thread_done = 0;
@@ -466,11 +484,10 @@ print_device(const char *uri,             /* I - Device URI */
 
     if (pthread_create(&sidechannel_thread_id, NULL, sidechannel_thread, NULL))
     {
-      _cupsLangPuts(stderr, _("WARNING: Couldn't create side channel\n"));
-      return CUPS_BACKEND_STOP;
+      _cupsLangPuts(stderr, _("ERROR: Fatal USB error!\n"));
+      fputs("DEBUG: Couldn't create side-channel thread!\n", stderr);
+      return (CUPS_BACKEND_STOP);
     }
-
-    sidechannel_started = 1;
   }
 
  /*
@@ -485,8 +502,9 @@ print_device(const char *uri,               /* I - Device URI */
 
   if (pthread_create(&read_thread_id, NULL, read_thread, NULL))
   {
-    _cupsLangPuts(stderr, _("WARNING: Couldn't create read channel\n"));
-    return CUPS_BACKEND_STOP;
+    _cupsLangPuts(stderr, _("ERROR: Fatal USB error!\n"));
+    fputs("DEBUG: Couldn't create read thread!\n", stderr);
+    return (CUPS_BACKEND_STOP);
   }
 
  /*
@@ -500,11 +518,11 @@ print_device(const char *uri,             /* I - Device URI */
 
   while (status == noErr && copies-- > 0)
   {
-    _cupsLangPuts(stderr, _("INFO: Sending data\n"));
+    _cupsLangPuts(stderr, _("INFO: Sending print data...\n"));
 
     if (print_fd != STDIN_FILENO)
     {
-      fputs("PAGE: 1 1", stderr);
+      fputs("PAGE: 1 1\n", stderr);
       lseek(print_fd, 0, SEEK_SET);
     }
 
@@ -568,8 +586,9 @@ print_device(const char *uri,               /* I - Device URI */
        }
        else if (errno != EAGAIN)
        {
-        _cupsLangPrintf(stderr, _("ERROR: select() returned %d\n"), (int)errno);
-        return CUPS_BACKEND_STOP;
+         _cupsLangPuts(stderr, _("ERROR: Unable to read print data!\n"));
+         perror("DEBUG: select");
+         return (CUPS_BACKEND_STOP);
        }
       }
 
@@ -600,7 +619,8 @@ print_device(const char *uri,               /* I - Device URI */
 
          if (errno != EAGAIN || errno != EINTR)
          {
-           perror("ERROR: Unable to read print data");
+           _cupsLangPuts(stderr, _("ERROR: Unable to read print data!\n"));
+           perror("DEBUG: read");
            return CUPS_BACKEND_STOP;
          }
 
@@ -644,8 +664,11 @@ print_device(const char *uri,              /* I - Device URI */
          */
 
          OSStatus err = (*g.classdriver)->Abort(g.classdriver);
-         _cupsLangPrintf(stderr, _("ERROR: %ld: (canceled:%ld)\n"),
-                         (long)status, (long)err);
+         _cupsLangPuts(stderr, _("ERROR: Unable to send print data!\n"));
+         fprintf(stderr, "DEBUG: USB class driver WritePipe returned %ld\n",
+                 (long)status);
+         fprintf(stderr, "DEBUG: USB class driver Abort returned %ld\n",
+                 (long)err);
          status = CUPS_BACKEND_STOP;
          break;
        }
@@ -669,7 +692,7 @@ print_device(const char *uri,               /* I - Device URI */
   * Wait for the side channel thread to exit...
   */
 
-  if (sidechannel_started)
+  if (have_sidechannel)
   {
     close(CUPS_SC_FD);
     pthread_mutex_lock(&g.readwrite_lock_mutex);
@@ -1004,25 +1027,21 @@ static Boolean list_device_cb(void *refcon,
     {
       CFStringRef make = NULL,  model = NULL, serial = NULL;
       char uristr[1024], makestr[1024], modelstr[1024], serialstr[1024];
-      char optionsstr[1024], idstr[1024];
+      char optionsstr[1024], idstr[1024], make_modelstr[1024];
 
       copy_deviceinfo(deviceIDString, &make, &model, &serial);
-
-      modelstr[0] = '/';
-
       CFStringGetCString(deviceIDString, idstr, sizeof(idstr),
                          kCFStringEncodingUTF8);
+      backendGetMakeModel(idstr, make_modelstr, sizeof(make_modelstr));
 
-      if (make)
-        CFStringGetCString(make, makestr, sizeof(makestr),
-                          kCFStringEncodingUTF8);
-      else
+      modelstr[0] = '/';
+
+      if (!CFStringGetCString(make, makestr, sizeof(makestr),
+                             kCFStringEncodingUTF8))
         strcpy(makestr, "Unknown");
 
-      if (model)
-       CFStringGetCString(model, &modelstr[1], sizeof(modelstr)-1,
-                          kCFStringEncodingUTF8);
-      else
+      if (!CFStringGetCString(model, &modelstr[1], sizeof(modelstr)-1,
+                             kCFStringEncodingUTF8))
         strcpy(modelstr + 1, "Printer");
 
       optionsstr[0] = '\0';
@@ -1037,18 +1056,8 @@ static Boolean list_device_cb(void *refcon,
       httpAssembleURI(HTTP_URI_CODING_ALL, uristr, sizeof(uristr), "usb", NULL, makestr, 0, modelstr);
       strncat(uristr, optionsstr, sizeof(uristr));
 
-     /*
-      * Fix common HP 1284 bug...
-      */
-
-      if (!strcasecmp(makestr, "Hewlett-Packard"))
-        strcpy(makestr, "HP");
-
-      if (!strncasecmp(modelstr + 1, "hp ", 3))
-        _cups_strcpy(modelstr + 1, modelstr + 4);
-
-      printf("direct %s \"%s %s\" \"%s %s USB\" \"%s\"\n", uristr, makestr,
-             &modelstr[1], makestr, &modelstr[1], idstr);
+      cupsBackendReport("direct", uristr, make_modelstr, make_modelstr, idstr,
+                        NULL);
 
       release_deviceinfo(&make, &model, &serial);
       CFRelease(deviceIDString);
@@ -1131,7 +1140,7 @@ static Boolean find_device_cb(void *refcon,
   if (!keepLooking && g.status_timer != NULL)
   {
     fputs("STATE: -offline-error\n", stderr);
-    _cupsLangPuts(stderr, _("INFO: Printer is now on-line.\n"));
+    _cupsLangPuts(stderr, _("INFO: Printer is now online.\n"));
     CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), g.status_timer, kCFRunLoopDefaultMode);
     CFRelease(g.status_timer);
     g.status_timer = NULL;
@@ -1149,7 +1158,7 @@ static void status_timer_cb(CFRunLoopTimerRef timer,
                            void *info)
 {
   fputs("STATE: +offline-error\n", stderr);
-  _cupsLangPuts(stderr, _("INFO: Printer is currently off-line.\n"));
+  _cupsLangPuts(stderr, _("INFO: Printer is offline.\n"));
 
   if (getenv("CLASS") != NULL)
   {
@@ -1184,9 +1193,19 @@ static void copy_deviceinfo(CFStringRef deviceIDString,
   CFStringRef serialKeys[] = { CFSTR("SN:"),  CFSTR("SERN:"), NULL };
 
   if (make != NULL)
-    *make = copy_value_for_key(deviceIDString, makeKeys);
+  {
+    if ((*make = copy_value_for_key(deviceIDString, makeKeys)) == NULL)
+      *make = CFStringCreateWithCString(kCFAllocatorDefault, "Unknown",
+                                        kCFStringEncodingUTF8);
+  }
+
   if (model != NULL)
-    *model = copy_value_for_key(deviceIDString, modelKeys);
+  {
+    if ((*model = copy_value_for_key(deviceIDString, modelKeys)) == NULL)
+      *model = CFStringCreateWithCString(kCFAllocatorDefault, "Printer",
+                                         kCFStringEncodingUTF8);
+  }
+
   if (serial != NULL)
     *serial = copy_value_for_key(deviceIDString, serialKeys);
 }
@@ -1679,7 +1698,7 @@ static void parse_options(char *options,
 
 /*!
  * @function   setup_cfLanguage
- * @abstract   Convert the contents of the CUPS 'LANG' environment
+ * @abstract   Convert the contents of the CUPS 'APPLE_LANGUAGE' environment
  *             variable into a one element CF array of languages.
  *
  * @discussion Each submitted job comes with a natural language. CUPS passes
@@ -1695,53 +1714,61 @@ static void setup_cfLanguage(void)
   CFArrayRef   langArray = NULL;
   const char   *requestedLang = NULL;
 
-  requestedLang = getenv("LANG");
+  if ((requestedLang = getenv("APPLE_LANGUAGE")) == NULL)
+    requestedLang = getenv("LANG");
+
   if (requestedLang != NULL)
   {
     lang[0] = CFStringCreateWithCString(kCFAllocatorDefault, requestedLang, kCFStringEncodingUTF8);
     langArray = CFArrayCreate(kCFAllocatorDefault, (const void **)lang, sizeof(lang) / sizeof(lang[0]), &kCFTypeArrayCallBacks);
 
     CFPreferencesSetAppValue(CFSTR("AppleLanguages"), langArray, kCFPreferencesCurrentApplication);
-    DEBUG_printf((stderr, "DEBUG: usb: AppleLanguages = \"%s\"\n", requestedLang));
+    fprintf(stderr, "DEBUG: usb: AppleLanguages=\"%s\"\n", requestedLang);
 
     CFRelease(lang[0]);
     CFRelease(langArray);
   }
   else
-    fputs("DEBUG: usb: LANG environment variable missing.\n", stderr);
+    fputs("DEBUG: usb: LANG and APPLE_LANGUAGE environment variables missing.\n", stderr);
 }
 
 #pragma mark -
-#if defined(__i386__)
+#if defined(__i386__) || defined(__x86_64__)
 /*!
- * @function   run_ppc_backend
+ * @function   run_legacy_backend
  *
- * @abstract   Starts child backend process running as a ppc executable.
+ * @abstract   Starts child backend process running as a ppc or i386 executable.
  *
  * @result     Never returns; always calls exit().
  *
  * @discussion
  */
-static void run_ppc_backend(int argc,
-                           char *argv[],
-                           int fd)
+static void run_legacy_backend(int argc,
+                              char *argv[],
+                              int fd)
 {
   int  i;
   int  exitstatus = 0;
   int  childstatus;
   pid_t        waitpid_status;
   char *my_argv[32];
-  char *usb_ppc_status;
+  char *usb_legacy_status;
 
-  /*
-   * If we're running as i386 and couldn't load the class driver (because they'it's
-   * ppc-only) then try to re-exec ourselves in ppc mode to try again. If we don't have
-   * a ppc architecture we may be running i386 again so guard against this by setting
-   * and testing an environment variable...
-   */
-  usb_ppc_status = getenv("USB_PPC_STATUS");
+ /*
+  * If we're running as x86_64 or i386 and couldn't load the class driver
+  * (because it's ppc or i386), then try to re-exec ourselves in ppc or i386
+  * mode to try again. If we don't have a ppc or i386 architecture we may be
+  * running with the same architecture again so guard against this by setting
+  * and testing an environment variable...
+  */
+
+#  ifdef __x86_64__
+  usb_legacy_status = getenv("USB_I386_STATUS");
+#  else
+  usb_legacy_status = getenv("USB_PPC_STATUS");
+#  endif /* __x86_64__ */
 
-  if (usb_ppc_status == NULL)
+  if (!usb_legacy_status)
   {
    /*
     * Setup a SIGTERM handler then block it before forking...
@@ -1760,55 +1787,49 @@ static void run_ppc_backend(int argc,
     sigaddset(&newmask, SIGTERM);
     sigprocmask(SIG_BLOCK, &newmask, &oldmask);
 
-    if ((child_pid = fork()) == 0)
-    {
-     /*
-      * Child comes here...
-      */
-
-      setenv("USB_PPC_STATUS", "1", false);
-
-     /*
-      * Unblock signals before doing the exec...
-      */
-
-      memset(&action, 0, sizeof(action));
-      sigemptyset(&action.sa_mask);
-      action.sa_handler = SIG_DFL;
-      sigaction(SIGTERM, &action, NULL);
+   /*
+    * Set the environment variable...
+    */
 
-      sigprocmask(SIG_SETMASK, &oldmask, NULL);
+#  ifdef __x86_64__
+    setenv("USB_I386_STATUS", "1", false);
+#  else
+    setenv("USB_PPC_STATUS", "1", false);
+#  endif /* __x86_64__ */
 
-     /*
-      * Tell the kernel the next exec call should favor the ppc architecture...
-      */
+   /*
+    * Tell the kernel to use the specified CPU architecture...
+    */
 
-      int mib[] = { CTL_KERN, KERN_AFFINITY, 1, 1 };
-      int namelen = 4;
-      sysctl(mib, namelen, NULL, NULL, NULL, 0);
+#  ifdef __x86_64__
+    cpu_type_t cpu = CPU_TYPE_I386;
+#  else
+    cpu_type_t cpu = CPU_TYPE_POWERPC;
+#  endif /* __x86_64__ */
+    size_t ocount = 0;
+    posix_spawnattr_t attrs;
 
-     /*
-      * Set up the arguments and call exec...
-      */
+    if (!posix_spawnattr_init(&attrs))
+    {
+      posix_spawnattr_setsigdefault(attrs, &oldmask);
+      posix_spawnattr_setbinpref_np(attrs, 1, &cpu, &ocount);
+    }
 
-      for (i = 0; i < argc && i < (sizeof(my_argv)/sizeof(my_argv[0])) - 1; i++)
-       my_argv[i] = argv[i];
+   /*
+    * Set up the arguments and call posix_spawn...
+    */
 
-      my_argv[i] = NULL;
+    for (i = 0; i < argc && i < (sizeof(my_argv) / sizeof(my_argv[0])) - 1; i ++)
+      my_argv[i] = argv[i];
 
-      execv("/usr/libexec/cups/backend/usb", my_argv);
+    my_argv[i] = NULL;
 
-      perror("/usr/libexec/cups/backend/usb");
-      exit(errno);
-    }
-    else if (child_pid < 0)
+    if (posix_spawn(&child_pid, "/usr/libexec/cups/backend/usb", NULL, &attrs,
+                    my_argv, environ))
     {
-     /*
-      * Error - couldn't fork a new process!
-      */
-
-      perror("fork");
-      exit(errno);
+      _cupsLangPrintf(stderr, _("Unable to use legacy USB class driver!\n"));
+      perror("DEBUG: Unable to exec /usr/libexec/cups/backend/usb");
+      exit(CUPS_BACKEND_STOP);
     }
 
    /*
@@ -1824,28 +1845,33 @@ static void run_ppc_backend(int argc,
     close(fd);
     close(1);
 
-    fprintf(stderr, "DEBUG: Started usb(ppc) backend (PID %d)\n", (int)child_pid);
+    fprintf(stderr, "DEBUG: Started usb(legacy) backend (PID %d)\n",
+            (int)child_pid);
 
     while ((waitpid_status = waitpid(child_pid, &childstatus, 0)) == (pid_t)-1 && errno == EINTR)
       usleep(1000);
 
     if (WIFSIGNALED(childstatus))
     {
-      exitstatus = WTERMSIG(childstatus);
-      fprintf(stderr, "DEBUG: usb(ppc) backend %d crashed on signal %d!\n", child_pid, exitstatus);
+      exitstatus = CUPS_BACKEND_STOP;
+      fprintf(stderr, "DEBUG: usb(legacy) backend %d crashed on signal %d!\n",
+              child_pid, WTERMSIG(childstatus));
     }
     else
     {
       if ((exitstatus = WEXITSTATUS(childstatus)) != 0)
-       fprintf(stderr, "DEBUG: usb(ppc) backend %d stopped with status %d!\n", child_pid, exitstatus);
+       fprintf(stderr,
+               "DEBUG: usb(legacy) backend %d stopped with status %d!\n",
+               child_pid, exitstatus);
       else
-       fprintf(stderr, "DEBUG: PID %d exited with no errors\n", child_pid);
+       fprintf(stderr, "DEBUG: usb(legacy) backend %d exited with no errors\n",
+               child_pid);
     }
   }
   else
   {
-    fputs("DEBUG: usb child running i386 again\n", stderr);
-    exitstatus = ENOENT;
+    fputs("DEBUG: usb(legacy) backend running native again\n", stderr);
+    exitstatus = CUPS_BACKEND_STOP;
   }
 
   exit(exitstatus);
@@ -1865,7 +1891,7 @@ static void sigterm_handler(int sig)
   exit(1);
 }
 
-#endif /* __i386__ */
+#endif /* __i386__ || __x86_64__ */
 
 
 #ifdef PARSE_PS_ERRORS
@@ -2033,5 +2059,5 @@ static void get_device_id(cups_sc_status_t *status,
 
 
 /*
- * End of "$Id: usb-darwin.c 6993 2007-09-28 18:05:28Z mike $".
+ * End of "$Id: usb-darwin.c 7460 2008-04-16 02:19:54Z mike $".
  */