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