Merge changes from CUPS 1.4svn-r7874.
[thirdparty/cups.git] / backend / socket.c
1 /*
2  * "$Id: socket.c 7583 2008-05-16 17:47:16Z mike $"
3  *
4  *   AppSocket backend for the Common UNIX Printing System (CUPS).
5  *
6  *   Copyright 2007-2008 by Apple Inc.
7  *   Copyright 1997-2007 by Easy Software Products, all rights reserved.
8  *
9  *   These coded instructions, statements, and computer programs are the
10  *   property of Apple Inc. and are protected by Federal copyright
11  *   law.  Distribution and use rights are outlined in the file "LICENSE.txt"
12  *   "LICENSE" which should have been included with this file.  If this
13  *   file is missing or damaged, see the license at "http://www.cups.org/".
14  *
15  *   This file is subject to the Apple OS-Developed Software exception.
16  *
17  * Contents:
18  *
19  *   main()    - Send a file to the printer or server.
20  *   wait_bc() - Wait for back-channel data...
21  */
22
23 /*
24  * Include necessary headers.
25  */
26
27 #include <cups/http-private.h>
28 #include "backend-private.h"
29 #include <stdarg.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32
33 #ifdef WIN32
34 #  include <winsock.h>
35 #else
36 #  include <unistd.h>
37 #  include <fcntl.h>
38 #  include <sys/socket.h>
39 #  include <netinet/in.h>
40 #  include <arpa/inet.h>
41 #  include <netdb.h>
42 #endif /* WIN32 */
43
44
45 /*
46  * Local functions...
47  */
48
49 static int      wait_bc(int device_fd, int secs);
50
51
52 /*
53  * 'main()' - Send a file to the printer or server.
54  *
55  * Usage:
56  *
57  *    printer-uri job-id user title copies options [file]
58  */
59
60 int                                     /* O - Exit status */
61 main(int  argc,                         /* I - Number of command-line arguments (6 or 7) */
62      char *argv[])                      /* I - Command-line arguments */
63 {
64   const char    *device_uri;            /* Device URI */
65   char          method[255],            /* Method in URI */
66                 hostname[1024],         /* Hostname */
67                 username[255],          /* Username info (not used) */
68                 resource[1024],         /* Resource info (not used) */
69                 *options,               /* Pointer to options */
70                 *name,                  /* Name of option */
71                 *value,                 /* Value of option */
72                 sep;                    /* Option separator */
73   int           print_fd;               /* Print file */
74   int           copies;                 /* Number of copies to print */
75   time_t        start_time,             /* Time of first connect */
76                 wait_time;              /* Time to wait before shutting down socket */
77   int           recoverable;            /* Recoverable error shown? */
78   int           contimeout;             /* Connection timeout */
79   int           waiteof;                /* Wait for end-of-file? */
80   int           port;                   /* Port number */
81   char          portname[255];          /* Port name */
82   int           delay;                  /* Delay for retries... */
83   int           device_fd;              /* AppSocket */
84   int           error;                  /* Error code (if any) */
85   http_addrlist_t *addrlist,            /* Address list */
86                 *addr;                  /* Connected address */
87   char          addrname[256];          /* Address name */
88   int           snmp_fd,                /* SNMP socket */
89                 start_count,            /* Page count via SNMP at start */
90                 page_count;             /* Page count via SNMP */
91   ssize_t       tbytes;                 /* Total number of bytes written */
92 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
93   struct sigaction action;              /* Actions for POSIX signals */
94 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
95
96
97  /*
98   * Make sure status messages are not buffered...
99   */
100
101   setbuf(stderr, NULL);
102
103  /*
104   * Ignore SIGPIPE signals...
105   */
106
107 #ifdef HAVE_SIGSET
108   sigset(SIGPIPE, SIG_IGN);
109 #elif defined(HAVE_SIGACTION)
110   memset(&action, 0, sizeof(action));
111   action.sa_handler = SIG_IGN;
112   sigaction(SIGPIPE, &action, NULL);
113 #else
114   signal(SIGPIPE, SIG_IGN);
115 #endif /* HAVE_SIGSET */
116
117  /*
118   * Check command-line...
119   */
120
121   if (argc == 1)
122   {
123     puts("network socket \"Unknown\" \"AppSocket/HP JetDirect\"");
124     return (CUPS_BACKEND_OK);
125   }
126   else if (argc < 6 || argc > 7)
127   {
128     _cupsLangPrintf(stderr,
129                     _("Usage: %s job-id user title copies options [file]\n"),
130                     argv[0]);
131     return (CUPS_BACKEND_FAILED);
132   }
133
134  /*
135   * If we have 7 arguments, print the file named on the command-line.
136   * Otherwise, send stdin instead...
137   */
138
139   if (argc == 6)
140   {
141     print_fd = 0;
142     copies   = 1;
143   }
144   else
145   {
146    /*
147     * Try to open the print file...
148     */
149
150     if ((print_fd = open(argv[6], O_RDONLY)) < 0)
151     {
152       _cupsLangPrintf(stderr,
153                       _("ERROR: Unable to open print file \"%s\": %s\n"),
154                       argv[6], strerror(errno));
155       return (CUPS_BACKEND_FAILED);
156     }
157
158     copies = atoi(argv[4]);
159   }
160
161  /*
162   * Extract the hostname and port number from the URI...
163   */
164
165   if ((device_uri = cupsBackendDeviceURI(argv)) == NULL)
166     return (CUPS_BACKEND_FAILED);
167
168   httpSeparateURI(HTTP_URI_CODING_ALL, device_uri,
169                   method, sizeof(method), username, sizeof(username),
170                   hostname, sizeof(hostname), &port,
171                   resource, sizeof(resource));
172
173   if (port == 0)
174     port = 9100;        /* Default to HP JetDirect/Tektronix PhaserShare */
175
176  /*
177   * Get options, if any...
178   */
179
180   waiteof    = 1;
181   contimeout = 7 * 24 * 60 * 60;
182
183   if ((options = strchr(resource, '?')) != NULL)
184   {
185    /*
186     * Yup, terminate the device name string and move to the first
187     * character of the options...
188     */
189
190     *options++ = '\0';
191
192    /*
193     * Parse options...
194     */
195
196     while (*options)
197     {
198      /*
199       * Get the name...
200       */
201
202       name = options;
203
204       while (*options && *options != '=' && *options != '+' && *options != '&')
205         options ++;
206
207       if ((sep = *options) != '\0')
208         *options++ = '\0';
209
210       if (sep == '=')
211       {
212        /*
213         * Get the value...
214         */
215
216         value = options;
217
218         while (*options && *options != '+' && *options != '&')
219           options ++;
220
221         if (*options)
222           *options++ = '\0';
223       }
224       else
225         value = (char *)"";
226
227      /*
228       * Process the option...
229       */
230
231       if (!strcasecmp(name, "waiteof"))
232       {
233        /*
234         * Set the wait-for-eof value...
235         */
236
237         waiteof = !value[0] || !strcasecmp(value, "on") ||
238                   !strcasecmp(value, "yes") || !strcasecmp(value, "true");
239       }
240       else if (!strcasecmp(name, "contimeout"))
241       {
242        /*
243         * Set the connection timeout...
244         */
245
246         if (atoi(value) > 0)
247           contimeout = atoi(value);
248       }
249     }
250   }
251
252  /*
253   * Then try to connect to the remote host...
254   */
255
256   recoverable = 0;
257   start_time  = time(NULL);
258
259   sprintf(portname, "%d", port);
260
261   if ((addrlist = httpAddrGetList(hostname, AF_UNSPEC, portname)) == NULL)
262   {
263     _cupsLangPrintf(stderr, _("ERROR: Unable to locate printer \'%s\'!\n"),
264                     hostname);
265     return (CUPS_BACKEND_STOP);
266   }
267
268   _cupsLangPrintf(stderr,
269                   _("INFO: Attempting to connect to host %s on port %d\n"),
270                   hostname, port);
271
272   fputs("STATE: +connecting-to-device\n", stderr);
273
274   for (delay = 5;;)
275   {
276     if ((addr = httpAddrConnect(addrlist, &device_fd)) == NULL)
277     {
278       error     = errno;
279       device_fd = -1;
280
281       if (getenv("CLASS") != NULL)
282       {
283        /*
284         * If the CLASS environment variable is set, the job was submitted
285         * to a class and not to a specific queue.  In this case, we want
286         * to abort immediately so that the job can be requeued on the next
287         * available printer in the class.
288         */
289
290         _cupsLangPuts(stderr,
291                       _("INFO: Unable to contact printer, queuing on next "
292                         "printer in class...\n"));
293
294        /*
295         * Sleep 5 seconds to keep the job from requeuing too rapidly...
296         */
297
298         sleep(5);
299
300         return (CUPS_BACKEND_FAILED);
301       }
302
303       if (error == ECONNREFUSED || error == EHOSTDOWN ||
304           error == EHOSTUNREACH)
305       {
306         if (contimeout && (time(NULL) - start_time) > contimeout)
307         {
308           _cupsLangPuts(stderr, _("ERROR: Printer not responding!\n"));
309           return (CUPS_BACKEND_FAILED);
310         }
311
312         recoverable = 1;
313
314         _cupsLangPrintf(stderr,
315                         _("WARNING: recoverable: Network host \'%s\' is busy; "
316                           "will retry in %d seconds...\n"),
317                         hostname, delay);
318
319         sleep(delay);
320
321         if (delay < 30)
322           delay += 5;
323       }
324       else
325       {
326         recoverable = 1;
327
328         _cupsLangPrintf(stderr, "DEBUG: Connection error: %s\n",
329                         strerror(errno));
330         _cupsLangPuts(stderr,
331                       _("ERROR: recoverable: Unable to connect to printer; "
332                         "will retry in 30 seconds...\n"));
333         sleep(30);
334       }
335     }
336     else
337       break;
338   }
339
340   if (recoverable)
341   {
342    /*
343     * If we've shown a recoverable error make sure the printer proxies
344     * have a chance to see the recovered message. Not pretty but
345     * necessary for now...
346     */
347
348     fputs("INFO: recovered: \n", stderr);
349     sleep(5);
350   }
351
352   fputs("STATE: -connecting-to-device\n", stderr);
353   _cupsLangPrintf(stderr, _("INFO: Connected to %s...\n"), hostname);
354
355 #ifdef AF_INET6
356   if (addr->addr.addr.sa_family == AF_INET6)
357     fprintf(stderr, "DEBUG: Connected to [%s]:%d (IPv6)...\n", 
358             httpAddrString(&addr->addr, addrname, sizeof(addrname)),
359             ntohs(addr->addr.ipv6.sin6_port));
360   else
361 #endif /* AF_INET6 */
362     if (addr->addr.addr.sa_family == AF_INET)
363       fprintf(stderr, "DEBUG: Connected to %s:%d (IPv4)...\n",
364               httpAddrString(&addr->addr, addrname, sizeof(addrname)),
365               ntohs(addr->addr.ipv4.sin_port));
366
367  /*
368   * See if the printer supports SNMP...
369   */
370
371   if ((snmp_fd = _cupsSNMPOpen(addr->addr.addr.sa_family)) >= 0)
372   {
373     if (backendSNMPSupplies(snmp_fd, &(addr->addr), &start_count, NULL))
374     {
375      /*
376       * No, close it...
377       */
378
379       _cupsSNMPClose(snmp_fd);
380       snmp_fd = -1;
381     }
382   }
383   else
384     start_count = 0;
385
386  /*
387   * Print everything...
388   */
389
390   tbytes = 0;
391
392   while (copies > 0 && tbytes >= 0)
393   {
394     copies --;
395
396     if (print_fd != 0)
397     {
398       fputs("PAGE: 1 1\n", stderr);
399       lseek(print_fd, 0, SEEK_SET);
400     }
401
402     tbytes = backendRunLoop(print_fd, device_fd, snmp_fd, &(addr->addr), 1,
403                             backendNetworkSideCB);
404
405     if (print_fd != 0 && tbytes >= 0)
406       _cupsLangPrintf(stderr,
407 #ifdef HAVE_LONG_LONG
408                       _("INFO: Sent print file, %lld bytes...\n"),
409 #else
410                       _("INFO: Sent print file, %ld bytes...\n"),
411 #endif /* HAVE_LONG_LONG */
412                       CUPS_LLCAST tbytes);
413   }
414
415  /*
416   * Wait up to 5 seconds to get any pending back-channel data...
417   */
418
419   wait_time = time(NULL) + 5;
420   while (wait_time >= time(NULL))
421     if (wait_bc(device_fd, 1) <= 0)
422       break;
423
424   if (waiteof)
425   {
426    /*
427     * Shutdown the socket and wait for the other end to finish...
428     */
429
430     _cupsLangPuts(stderr,
431                   _("INFO: Print file sent, waiting for printer to finish...\n"));
432
433     shutdown(device_fd, 1);
434
435     while (wait_bc(device_fd, 90) > 0);
436   }
437
438  /*
439   * Collect the final page count as needed...
440   */
441
442   if (snmp_fd >= 0 && 
443       !backendSNMPSupplies(snmp_fd, &(addr->addr), &page_count, NULL) &&
444       page_count > start_count)
445     fprintf(stderr, "PAGE: total %d\n", page_count - start_count);
446
447  /*
448   * Close the socket connection...
449   */
450
451   close(device_fd);
452
453   httpAddrFreeList(addrlist);
454
455  /*
456   * Close the input file and return...
457   */
458
459   if (print_fd != 0)
460     close(print_fd);
461
462   if (tbytes >= 0)
463     _cupsLangPuts(stderr, _("INFO: Ready to print.\n"));
464
465   return (tbytes < 0 ? CUPS_BACKEND_FAILED : CUPS_BACKEND_OK);
466 }
467
468
469 /*
470  * 'wait_bc()' - Wait for back-channel data...
471  */
472
473 static int                              /* O - # bytes read or -1 on error */
474 wait_bc(int device_fd,                  /* I - Socket */
475         int secs)                       /* I - Seconds to wait */
476 {
477   struct timeval timeout;               /* Timeout for select() */
478   fd_set        input;                  /* Input set for select() */
479   ssize_t       bytes;                  /* Number of back-channel bytes read */
480   char          buffer[1024];           /* Back-channel buffer */
481
482
483  /*
484   * Wait up to "secs" seconds for backchannel data...
485   */
486
487   timeout.tv_sec  = secs;
488   timeout.tv_usec = 0;
489
490   FD_ZERO(&input);
491   FD_SET(device_fd, &input);
492
493   if (select(device_fd + 1, &input, NULL, NULL, &timeout) > 0)
494   {
495    /*
496     * Grab the data coming back and spit it out to stderr...
497     */
498
499     if ((bytes = read(device_fd, buffer, sizeof(buffer))) > 0)
500     {
501       fprintf(stderr, "DEBUG: Received %d bytes of back-channel data!\n",
502               (int)bytes);
503       cupsBackChannelWrite(buffer, bytes, 1.0);
504     }
505
506     return (bytes);
507   }
508   else
509     return (-1);
510 }
511
512
513 /*
514  * End of "$Id: socket.c 7583 2008-05-16 17:47:16Z mike $".
515  */