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