Imported Upstream version 2.2.2
[platform/upstream/cups.git] / backend / testbackend.c
1 /*
2  * Backend test program for CUPS.
3  *
4  * Copyright 2007-2014 by Apple Inc.
5  * Copyright 1997-2005 by Easy Software Products, all rights reserved.
6  *
7  * These coded instructions, statements, and computer programs are the
8  * property of Apple Inc. and are protected by Federal copyright
9  * law.  Distribution and use rights are outlined in the file "LICENSE.txt"
10  * "LICENSE" which should have been included with this file.  If this
11  * file is missing or damaged, see the license at "http://www.cups.org/".
12  *
13  * This file is subject to the Apple OS-Developed Software exception.
14  */
15
16 /*
17  * Include necessary headers.
18  */
19
20 #include <cups/string-private.h>
21 #include <cups/cups.h>
22 #include <cups/sidechannel.h>
23 #include <unistd.h>
24 #include <fcntl.h>
25 #include <sys/wait.h>
26 #include <signal.h>
27
28
29 /*
30  * Local globals...
31  */
32
33 static int      job_canceled = 0;
34
35
36 /*
37  * Local functions...
38  */
39
40 static void     sigterm_handler(int sig);
41 static void     usage(void) __attribute__((noreturn));
42 static void     walk_cb(const char *oid, const char *data, int datalen,
43                         void *context);
44
45
46 /*
47  * 'main()' - Run the named backend.
48  *
49  * Usage:
50  *
51  *    testbackend [-s] [-t] device-uri job-id user title copies options [file]
52  */
53
54 int                                     /* O - Exit status */
55 main(int  argc,                         /* I - Number of command-line args */
56      char *argv[])                      /* I - Command-line arguments */
57 {
58   int           first_arg,              /* First argument for backend */
59                 do_cancel = 0,          /* Simulate a cancel-job via SIGTERM */
60                 do_ps = 0,              /* Do PostScript query+test? */
61                 do_pcl = 0,             /* Do PCL query+test? */
62                 do_side_tests = 0,      /* Test side-channel ops? */
63                 do_trickle = 0,         /* Trickle data to backend */
64                 do_walk = 0,            /* Do OID lookup (0) or walking (1) */
65                 show_log = 0;           /* Show log messages from backends? */
66   const char    *oid = ".1.3.6.1.2.1.43.10.2.1.4.1.1";
67                                         /* OID to lookup or walk */
68   char          scheme[255],            /* Scheme in URI == backend */
69                 backend[1024],          /* Backend path */
70                 libpath[1024],          /* Path for libcups */
71                 *ptr;                   /* Pointer into path */
72   const char    *serverbin;             /* CUPS_SERVERBIN environment variable */
73   int           fd,                     /* Temporary file descriptor */
74                 back_fds[2],            /* Back-channel pipe */
75                 side_fds[2],            /* Side-channel socket */
76                 data_fds[2],            /* Data pipe */
77                 back_pid = -1,          /* Backend process ID */
78                 data_pid = -1,          /* Trickle process ID */
79                 pid,                    /* Process ID */
80                 status;                 /* Exit status */
81
82
83  /*
84   * Get the current directory and point the run-time linker at the "cups"
85   * subdirectory...
86   */
87
88   if (getcwd(libpath, sizeof(libpath)) &&
89       (ptr = strrchr(libpath, '/')) != NULL && !strcmp(ptr, "/backend"))
90   {
91     strlcpy(ptr, "/cups", sizeof(libpath) - (size_t)(ptr - libpath));
92     if (!access(libpath, 0))
93     {
94 #ifdef __APPLE__
95       fprintf(stderr, "Setting DYLD_LIBRARY_PATH to \"%s\".\n", libpath);
96       setenv("DYLD_LIBRARY_PATH", libpath, 1);
97 #else
98       fprintf(stderr, "Setting LD_LIBRARY_PATH to \"%s\".\n", libpath);
99       setenv("LD_LIBRARY_PATH", libpath, 1);
100 #endif /* __APPLE__ */
101     }
102     else
103       perror(libpath);
104   }
105
106  /*
107   * See if we have side-channel tests to do...
108   */
109
110   for (first_arg = 1;
111        argv[first_arg] && argv[first_arg][0] == '-';
112        first_arg ++)
113     if (!strcmp(argv[first_arg], "-d"))
114       show_log = 1;
115     else if (!strcmp(argv[first_arg], "-cancel"))
116       do_cancel = 1;
117     else if (!strcmp(argv[first_arg], "-pcl"))
118       do_pcl = 1;
119     else if (!strcmp(argv[first_arg], "-ps"))
120       do_ps = 1;
121     else if (!strcmp(argv[first_arg], "-s"))
122       do_side_tests = 1;
123     else if (!strcmp(argv[first_arg], "-t"))
124       do_trickle = 1;
125     else if (!strcmp(argv[first_arg], "-get") && (first_arg + 1) < argc)
126     {
127       first_arg ++;
128
129       do_side_tests = 1;
130       oid           = argv[first_arg];
131     }
132     else if (!strcmp(argv[first_arg], "-walk") && (first_arg + 1) < argc)
133     {
134       first_arg ++;
135
136       do_side_tests = 1;
137       do_walk       = 1;
138       oid           = argv[first_arg];
139     }
140     else
141       usage();
142
143   argc -= first_arg;
144   if (argc < 6 || argc > 7 || (argc == 7 && do_trickle))
145     usage();
146
147  /*
148   * Extract the scheme from the device-uri - that's the program we want to
149   * execute.
150   */
151
152   if (sscanf(argv[first_arg], "%254[^:]", scheme) != 1)
153   {
154     fputs("testbackend: Bad device-uri - no colon!\n", stderr);
155     return (1);
156   }
157
158   if (!access(scheme, X_OK))
159     strlcpy(backend, scheme, sizeof(backend));
160   else
161   {
162     if ((serverbin = getenv("CUPS_SERVERBIN")) == NULL)
163       serverbin = CUPS_SERVERBIN;
164
165     snprintf(backend, sizeof(backend), "%s/backend/%s", serverbin, scheme);
166     if (access(backend, X_OK))
167     {
168       fprintf(stderr, "testbackend: Unknown device scheme \"%s\"!\n", scheme);
169       return (1);
170     }
171   }
172
173  /*
174   * Create the back-channel pipe and side-channel socket...
175   */
176
177   open("/dev/null", O_WRONLY);          /* Make sure fd 3 and 4 are used */
178   open("/dev/null", O_WRONLY);
179
180   pipe(back_fds);
181   fcntl(back_fds[0], F_SETFL, fcntl(back_fds[0], F_GETFL) | O_NONBLOCK);
182   fcntl(back_fds[1], F_SETFL, fcntl(back_fds[1], F_GETFL) | O_NONBLOCK);
183
184   socketpair(AF_LOCAL, SOCK_STREAM, 0, side_fds);
185   fcntl(side_fds[0], F_SETFL, fcntl(side_fds[0], F_GETFL) | O_NONBLOCK);
186   fcntl(side_fds[1], F_SETFL, fcntl(side_fds[1], F_GETFL) | O_NONBLOCK);
187
188  /*
189   * Execute the trickle process as needed...
190   */
191
192   if (do_trickle || do_pcl || do_ps || do_cancel)
193   {
194     pipe(data_fds);
195
196     signal(SIGTERM, sigterm_handler);
197
198     if ((data_pid = fork()) == 0)
199     {
200      /*
201       * Trickle/query child comes here.  Rearrange file descriptors so that
202       * FD 1, 3, and 4 point to the backend...
203       */
204
205       if ((fd = open("/dev/null", O_RDONLY)) != 0)
206       {
207         dup2(fd, 0);
208         close(fd);
209       }
210
211       if (data_fds[1] != 1)
212       {
213         dup2(data_fds[1], 1);
214         close(data_fds[1]);
215       }
216       close(data_fds[0]);
217
218       if (back_fds[0] != 3)
219       {
220         dup2(back_fds[0], 3);
221         close(back_fds[0]);
222       }
223       close(back_fds[1]);
224
225       if (side_fds[0] != 4)
226       {
227         dup2(side_fds[0], 4);
228         close(side_fds[0]);
229       }
230       close(side_fds[1]);
231
232       if (do_trickle)
233       {
234        /*
235         * Write 10 spaces, 1 per second...
236         */
237
238         int i;                          /* Looping var */
239
240         for (i = 0; i < 10; i ++)
241         {
242           write(1, " ", 1);
243           sleep(1);
244         }
245       }
246       else if (do_cancel)
247       {
248        /*
249         * Write PS or PCL lines until we see SIGTERM...
250         */
251
252         int     line = 0, page = 0;     /* Current line and page */
253         ssize_t bytes;                  /* Number of bytes of response data */
254         char    buffer[1024];           /* Output buffer */
255
256
257         if (do_pcl)
258           write(1, "\033E", 2);
259         else
260           write(1, "%!\n/Courier findfont 12 scalefont setfont 0 setgray\n", 52);
261
262         while (!job_canceled)
263         {
264           if (line == 0)
265           {
266             page ++;
267
268             if (do_pcl)
269               snprintf(buffer, sizeof(buffer), "PCL Page %d\r\n\r\n", page);
270             else
271               snprintf(buffer, sizeof(buffer),
272                        "18 732 moveto (PS Page %d) show\n", page);
273
274             write(1, buffer, strlen(buffer));
275           }
276
277           line ++;
278
279           if (do_pcl)
280             snprintf(buffer, sizeof(buffer), "Line %d\r\n", line);
281           else
282             snprintf(buffer, sizeof(buffer), "18 %d moveto (Line %d) show\n",
283                      720 - line * 12, line);
284
285           write(1, buffer, strlen(buffer));
286
287           if (line >= 55)
288           {
289            /*
290             * Eject after 55 lines...
291             */
292
293             line = 0;
294             if (do_pcl)
295               write(1, "\014", 1);
296             else
297               write(1, "showpage\n", 9);
298           }
299
300          /*
301           * Check for back-channel data...
302           */
303
304           if ((bytes = cupsBackChannelRead(buffer, sizeof(buffer), 0)) > 0)
305             write(2, buffer, (size_t)bytes);
306
307          /*
308           * Throttle output to ~100hz...
309           */
310
311           usleep(10000);
312         }
313
314        /*
315         * Eject current page with info...
316         */
317
318         if (do_pcl)
319           snprintf(buffer, sizeof(buffer),
320                    "Canceled on line %d of page %d\r\n\014\033E", line, page);
321         else
322           snprintf(buffer, sizeof(buffer),
323                    "\n18 %d moveto (Canceled on line %d of page %d)\nshowpage\n",
324                    720 - line * 12, line, page);
325
326         write(1, buffer, strlen(buffer));
327
328        /*
329         * See if we get any back-channel data...
330         */
331
332         while ((bytes = cupsBackChannelRead(buffer, sizeof(buffer), 5.0)) > 0)
333           write(2, buffer, (size_t)bytes);
334
335         exit(0);
336       }
337       else
338       {
339        /*
340         * Do PS or PCL query + test pages.
341         */
342
343         char            buffer[1024];   /* Buffer for response data */
344         ssize_t         bytes;          /* Number of bytes of response data */
345         double          timeout;        /* Timeout */
346         const char      *data;          /* Data to send */
347         static const char *pcl_data =   /* PCL data */
348                 "\033%-12345X@PJL\r\n"
349                 "@PJL JOB NAME = \"Hello, World!\"\r\n"
350                 "@PJL INFO USTATUS\r\n"
351                 "@PJL ENTER LANGUAGE = PCL\r\n"
352                 "\033E"
353                 "Hello, World!\n"
354                 "\014"
355                 "\033%-12345X@PJL\r\n"
356                 "@PJL EOJ NAME=\"Hello, World!\"\r\n"
357                 "\033%-12345X";
358         static const char *ps_data =    /* PostScript data */
359                 "%!\n"
360                 "save\n"
361                 "product = flush\n"
362                 "currentpagedevice /PageSize get aload pop\n"
363                 "2 copy gt {exch} if\n"
364                 "(Unknown)\n"
365                 "19 dict\n"
366                 "dup [612 792] (Letter) put\n"
367                 "dup [612 1008] (Legal) put\n"
368                 "dup [612 935] (w612h935) put\n"
369                 "dup [522 756] (Executive) put\n"
370                 "dup [595 842] (A4) put\n"
371                 "dup [420 595] (A5) put\n"
372                 "dup [499 709] (ISOB5) put\n"
373                 "dup [516 728] (B5) put\n"
374                 "dup [612 936] (w612h936) put\n"
375                 "dup [284 419] (Postcard) put\n"
376                 "dup [419.5 567] (DoublePostcard) put\n"
377                 "dup [558 774] (w558h774) put\n"
378                 "dup [553 765] (w553h765) put\n"
379                 "dup [522 737] (w522h737) put\n"
380                 "dup [499 709] (EnvISOB5) put\n"
381                 "dup [297 684] (Env10) put\n"
382                 "dup [459 649] (EnvC5) put\n"
383                 "dup [312 624] (EnvDL) put\n"
384                 "dup [279 540] (EnvMonarch) put\n"
385                 "{ exch aload pop 4 index sub abs 5 le exch\n"
386                 "  5 index sub abs 5 le and\n"
387                 "  {exch pop exit} {pop} ifelse\n"
388                 "} bind forall\n"
389                 "= flush pop pop\n"
390                 "/Courier findfont 12 scalefont setfont\n"
391                 "0 setgray 36 720 moveto (Hello, ) show product show (!) show\n"
392                 "showpage\n"
393                 "restore\n"
394                 "\004";
395
396
397         if (do_pcl)
398           data = pcl_data;
399         else
400           data = ps_data;
401
402         write(1, data, strlen(data));
403         write(2, "DEBUG: START\n", 13);
404         timeout = 60.0;
405         while ((bytes = cupsBackChannelRead(buffer, sizeof(buffer),
406                                             timeout)) > 0)
407         {
408           write(2, buffer, (size_t)bytes);
409           timeout = 5.0;
410         }
411         write(2, "\nDEBUG: END\n", 12);
412       }
413
414       exit(0);
415     }
416     else if (data_pid < 0)
417     {
418       perror("testbackend: Unable to fork");
419       return (1);
420     }
421   }
422   else
423     data_fds[0] = data_fds[1] = -1;
424
425  /*
426   * Execute the backend...
427   */
428
429   if ((back_pid = fork()) == 0)
430   {
431    /*
432     * Child comes here...
433     */
434
435     if (do_trickle || do_ps || do_pcl || do_cancel)
436     {
437       if (data_fds[0] != 0)
438       {
439         dup2(data_fds[0], 0);
440         close(data_fds[0]);
441       }
442       close(data_fds[1]);
443     }
444
445     if (!show_log)
446     {
447       if ((fd = open("/dev/null", O_WRONLY)) != 2)
448       {
449         dup2(fd, 2);
450         close(fd);
451       }
452     }
453
454     if (back_fds[1] != 3)
455     {
456       dup2(back_fds[1], 3);
457       close(back_fds[0]);
458     }
459     close(back_fds[1]);
460
461     if (side_fds[1] != 4)
462     {
463       dup2(side_fds[1], 4);
464       close(side_fds[0]);
465     }
466     close(side_fds[1]);
467
468     execv(backend, argv + first_arg);
469     fprintf(stderr, "testbackend: Unable to execute \"%s\": %s\n", backend,
470             strerror(errno));
471     return (errno);
472   }
473   else if (back_pid < 0)
474   {
475     perror("testbackend: Unable to fork");
476     return (1);
477   }
478
479  /*
480   * Parent comes here, setup back and side channel file descriptors...
481   */
482
483   if (do_trickle || do_ps || do_pcl || do_cancel)
484   {
485     close(data_fds[0]);
486     close(data_fds[1]);
487   }
488
489   if (back_fds[0] != 3)
490   {
491     dup2(back_fds[0], 3);
492     close(back_fds[0]);
493   }
494   close(back_fds[1]);
495
496   if (side_fds[0] != 4)
497   {
498     dup2(side_fds[0], 4);
499     close(side_fds[0]);
500   }
501   close(side_fds[1]);
502
503  /*
504   * Do side-channel tests as needed, then wait for the backend...
505   */
506
507   if (do_side_tests)
508   {
509     int                 length;         /* Length of buffer */
510     char                buffer[2049];   /* Buffer for reponse */
511     cups_sc_status_t    scstatus;       /* Status of side-channel command */
512     static const char * const statuses[] =
513     {
514       "CUPS_SC_STATUS_NONE",            /* No status */
515       "CUPS_SC_STATUS_OK",              /* Operation succeeded */
516       "CUPS_SC_STATUS_IO_ERROR",        /* An I/O error occurred */
517       "CUPS_SC_STATUS_TIMEOUT",         /* The backend did not respond */
518       "CUPS_SC_STATUS_NO_RESPONSE",     /* The device did not respond */
519       "CUPS_SC_STATUS_BAD_MESSAGE",     /* The command/response message was invalid */
520       "CUPS_SC_STATUS_TOO_BIG",         /* Response too big */
521       "CUPS_SC_STATUS_NOT_IMPLEMENTED"  /* Command not implemented */
522     };
523
524
525     sleep(2);
526
527     length   = 0;
528     scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_DRAIN_OUTPUT, buffer,
529                                         &length, 60.0);
530     printf("CUPS_SC_CMD_DRAIN_OUTPUT returned %s\n", statuses[scstatus]);
531
532     length   = 1;
533     scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_GET_BIDI, buffer,
534                                         &length, 5.0);
535     printf("CUPS_SC_CMD_GET_BIDI returned %s, %d\n", statuses[scstatus], buffer[0]);
536
537     length   = sizeof(buffer) - 1;
538     scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_GET_DEVICE_ID, buffer,
539                                         &length, 5.0);
540     buffer[length] = '\0';
541     printf("CUPS_SC_CMD_GET_DEVICE_ID returned %s, \"%s\"\n",
542            statuses[scstatus], buffer);
543
544     length   = 1;
545     scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_GET_STATE, buffer,
546                                         &length, 5.0);
547     printf("CUPS_SC_CMD_GET_STATE returned %s, %02X\n", statuses[scstatus],
548            buffer[0] & 255);
549
550     if (do_walk)
551     {
552      /*
553       * Walk the OID tree...
554       */
555
556       scstatus = cupsSideChannelSNMPWalk(oid, 5.0, walk_cb, NULL);
557       printf("CUPS_SC_CMD_SNMP_WALK returned %s\n", statuses[scstatus]);
558     }
559     else
560     {
561      /*
562       * Lookup the same OID twice...
563       */
564
565       length   = sizeof(buffer);
566       scstatus = cupsSideChannelSNMPGet(oid, buffer, &length, 5.0);
567       printf("CUPS_SC_CMD_SNMP_GET %s returned %s, %d bytes (%s)\n", oid,
568              statuses[scstatus], (int)length, buffer);
569
570       length   = sizeof(buffer);
571       scstatus = cupsSideChannelSNMPGet(oid, buffer, &length, 5.0);
572       printf("CUPS_SC_CMD_SNMP_GET %s returned %s, %d bytes (%s)\n", oid,
573              statuses[scstatus], (int)length, buffer);
574     }
575
576     length   = 0;
577     scstatus = cupsSideChannelDoRequest(CUPS_SC_CMD_SOFT_RESET, buffer,
578                                         &length, 5.0);
579     printf("CUPS_SC_CMD_SOFT_RESET returned %s\n", statuses[scstatus]);
580   }
581
582   if (do_cancel)
583   {
584     sleep(1);
585     kill(data_pid, SIGTERM);
586     kill(back_pid, SIGTERM);
587   }
588
589   while ((pid = wait(&status)) > 0)
590   {
591     if (status)
592     {
593       if (WIFEXITED(status))
594         printf("%s exited with status %d!\n",
595                pid == back_pid ? backend : "test",
596                WEXITSTATUS(status));
597       else
598         printf("%s crashed with signal %d!\n",
599                pid == back_pid ? backend : "test",
600                WTERMSIG(status));
601     }
602   }
603
604  /*
605   * Exit accordingly...
606   */
607
608   return (status != 0);
609 }
610
611
612 /*
613  * 'sigterm_handler()' - Flag when we get SIGTERM.
614  */
615
616 static void
617 sigterm_handler(int sig)                /* I - Signal */
618 {
619   (void)sig;
620
621   job_canceled = 1;
622 }
623
624
625 /*
626  * 'usage()' - Show usage information.
627  */
628
629 static void
630 usage(void)
631 {
632   puts("Usage: testbackend [-cancel] [-d] [-ps | -pcl] [-s [-get OID] "
633        "[-walk OID]] [-t] device-uri job-id user title copies options [file]");
634   puts("");
635   puts("Options:");
636   puts("  -cancel     Simulate a canceled print job after 2 seconds.");
637   puts("  -d          Show log messages from backend.");
638   puts("  -get OID    Lookup the specified SNMP OID.");
639   puts("              (.1.3.6.1.2.1.43.10.2.1.4.1.1 is a good one for printers)");
640   puts("  -pcl        Send PCL+PJL query and test page to backend.");
641   puts("  -ps         Send PostScript query and test page to backend.");
642   puts("  -s          Do side-channel + SNMP tests.");
643   puts("  -t          Send spaces slowly to backend ('trickle').");
644   puts("  -walk OID   Walk the specified SNMP OID.");
645   puts("              (.1.3.6.1.2.1.43 is a good one for printers)");
646
647   exit(1);
648 }
649
650
651 /*
652  * 'walk_cb()' - Show results of cupsSideChannelSNMPWalk...
653  */
654
655 static void
656 walk_cb(const char *oid,                /* I - OID */
657         const char *data,               /* I - Data */
658         int        datalen,             /* I - Length of data */
659         void       *context)            /* I - Context (unused) */
660 {
661   char temp[80];
662
663   (void)context;
664
665   if ((size_t)datalen > (sizeof(temp) - 1))
666   {
667     memcpy(temp, data, sizeof(temp) - 1);
668     temp[sizeof(temp) - 1] = '\0';
669   }
670   else
671   {
672     memcpy(temp, data, (size_t)datalen);
673     temp[datalen] = '\0';
674   }
675
676   printf("CUPS_SC_CMD_SNMP_WALK %s, %d bytes (%s)\n", oid, datalen, temp);
677 }