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