1 /*
2 * beh ("Backend Error Handler") wrapper backend to extend the possibilities
3 * of handling errors of backends.
4 *
5 * Copyright 2015 by Till Kamppeter
6 *
7 * This is based on dnssd.c of CUPS
8 * dnssd.c copyright notice is follows:
9 *
10 * Copyright 2008-2015 by Apple Inc.
11 *
12 * These coded instructions, statements, and computer programs are the
13 * property of Apple Inc. and are protected by Federal copyright
14 * law. Distribution and use rights are outlined in the file "COPYING"
15 * which should have been included with this file.
16 */
17
18 /*
19 * Include necessary headers.
20 */
21
22 #include "backend-private.h"
23 #include <cups/array.h>
24 #include <ctype.h>
25 #include <sys/wait.h>
26
27 /*
28 * Local globals...
29 */
30
31 static volatile int job_canceled = 0; /* Set to 1 on SIGTERM */
32
33 /*
34 * Local functions...
35 */
36
37 static int call_backend(char *uri, int argc, char **argv,
38 char *tempfile);
39 static void sigterm_handler(int sig);
40
41
42 /*
43 * 'main()' - Browse for printers.
44 */
45
46 int /* O - Exit status */
main(int argc,char * argv[])47 main(int argc, /* I - Number of command-line args */
48 char *argv[]) { /* I - Command-line arguments */
49 char *uri, *ptr, *filename;
50 char tmpfilename[1024], buf[8192];
51 int dd, att, delay, retval;
52 #if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
53 struct sigaction action; /* Actions for POSIX signals */
54 #endif /* HAVE_SIGACTION && !HAVE_SIGSET */
55
56 /*
57 * Don't buffer stderr, and catch SIGTERM...
58 */
59
60 setbuf(stderr, NULL);
61
62 #ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
63 sigset(SIGTERM, sigterm_handler);
64 #elif defined(HAVE_SIGACTION)
65 memset(&action, 0, sizeof(action));
66
67 sigemptyset(&action.sa_mask);
68 action.sa_handler = sigterm_handler;
69 sigaction(SIGTERM, &action, NULL);
70 #else
71 signal(SIGTERM, sigterm_handler);
72 #endif /* HAVE_SIGSET */
73
74 /*
75 * Check command-line...
76 */
77
78 if (argc == 1) {
79 if ((ptr = strrchr(argv[0], '/')) != NULL)
80 ptr ++;
81 else
82 ptr = argv[0];
83 printf("network %s \"Unknown\" \"Backend Error Handler\"\n",
84 ptr);
85 return (CUPS_BACKEND_OK);
86 } else if (argc < 6) {
87 fprintf(stderr,
88 "Usage: %s job-id user title copies options [file]\n",
89 argv[0]);
90 return (CUPS_BACKEND_FAILED);
91 }
92
93 /*
94 * Read out the parameters
95 */
96
97 uri = getenv("DEVICE_URI");
98 if (!uri) {
99 fprintf(stderr,
100 "ERROR: No device URI supplied!");
101 return (CUPS_BACKEND_FAILED);
102 }
103
104 ptr = strstr(uri, ":/");
105 if (!ptr) goto bad_uri;
106 ptr += 2;
107 if (*ptr == '0')
108 dd = 0;
109 else if (*ptr == '1')
110 dd = 1;
111 else
112 goto bad_uri;
113 ptr ++;
114 if (*ptr != '/') goto bad_uri;
115 ptr ++;
116 att = 0;
117 while (isdigit(*ptr)) {
118 att = att * 10 + (int)(*ptr) - 48;
119 ptr ++;
120 }
121 if (*ptr != '/') goto bad_uri;
122 ptr ++;
123 delay = 0;
124 while (isdigit(*ptr)) {
125 delay = delay * 10 + (int)(*ptr) - 48;
126 ptr ++;
127 }
128 if (*ptr != '/') goto bad_uri;
129 ptr ++;
130 fprintf(stderr,
131 "DEBUG: beh: Don't disable: %d; Attempts: %d; Delay: %d; Destination URI: %s\n",
132 dd, att, delay, ptr);
133
134 /*
135 * If reading from stdin, write everything into a temporary file
136 */
137
138 if (argc == 6) {
139 char *tmpdir;
140 int fd;
141 FILE *tmpfile;
142 size_t bytes;
143
144 tmpdir = getenv("TMPDIR");
145 if (!tmpdir)
146 tmpdir = "/tmp";
147 snprintf(tmpfilename, sizeof(tmpfilename), "%s/beh-XXXXXX", tmpdir);
148 fd = mkstemp(tmpfilename);
149 if (fd < 0) {
150 fprintf(stderr,
151 "ERROR: beh: Could not create temporary file: %s\n",
152 strerror(errno));
153 return (CUPS_BACKEND_FAILED);
154 }
155 tmpfile = fdopen(fd, "r+");
156 while ((bytes = fread(buf, 1, sizeof(buf), stdin)))
157 fwrite(buf, 1, bytes, tmpfile);
158 fclose(tmpfile);
159
160 filename = tmpfilename;
161 } else {
162 tmpfilename[0] = '\0';
163 filename = argv[6];
164 }
165
166 /*
167 * Do it!
168 */
169
170 while ((retval = call_backend(ptr, argc, argv, filename)) !=
171 CUPS_BACKEND_OK &&
172 !job_canceled) {
173 if (att > 0) {
174 att --;
175 if (att == 0)
176 break;
177 }
178 if (delay > 0)
179 sleep (delay);
180 }
181
182 if (strlen(tmpfilename) > 0)
183 unlink(tmpfilename);
184
185 /*
186 * Return the exit value of the backend only if requested
187 */
188
189 if (!dd)
190 return (retval);
191 else
192 return (CUPS_BACKEND_OK);
193
194 /*
195 * Error handling
196 */
197
198 bad_uri:
199
200 fprintf(stderr,
201 "ERROR: URI must be \"beh:/<dd>/<att>/<delay>/<original uri>\"!\n");
202 return (CUPS_BACKEND_FAILED);
203 }
204
205
206 /*
207 * 'call_backend()' - Execute the command line of the destination backend
208 */
209
210 static int
call_backend(char * uri,int argc,char ** argv,char * filename)211 call_backend(char *uri, /* I - URI of final destination */
212 int argc, /* I - Number of command line
213 arguments */
214 char **argv, /* I - Command-line arguments */
215 char *filename) { /* I - File name of input data */
216 const char *cups_serverbin; /* Location of programs */
217 char *backend_argv[8]; // Arguments for called CUPS backend
218 char scheme[1024], // Scheme from URI
219 *ptr, // Pointer into scheme
220 backend_path[2048]; // Backend path
221 int pid,
222 wait_pid,
223 wait_status,
224 retval = 0;
225 int bytes;
226
227 /*
228 * Build the backend command line...
229 */
230
231 scheme[0] = '\0';
232 strncat(scheme, uri, sizeof(scheme) - 1);
233 if ((ptr = strchr(scheme, ':')) != NULL)
234 *ptr = '\0';
235 else
236 {
237 fprintf(stderr,
238 "ERROR: beh: Invalid URI, no colon (':') to mark end of scheme part.\n");
239 exit (CUPS_BACKEND_FAILED);
240 }
241 if (strchr(scheme, '/'))
242 {
243 fprintf(stderr,
244 "ERROR: beh: Invalid URI, scheme contains a slash ('/').\n");
245 exit (CUPS_BACKEND_FAILED);
246 }
247 if (!strcmp(scheme, ".") || !strcmp(scheme, ".."))
248 {
249 fprintf(stderr,
250 "ERROR: beh: Invalid URI, scheme (\"%s\") is a directory.\n",
251 scheme);
252 exit (CUPS_BACKEND_FAILED);
253 }
254
255 if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
256 cups_serverbin = CUPS_SERVERBIN;
257
258 if (!strncasecmp(uri, "file:", 5) || uri[0] == '/') {
259 fprintf(stderr,
260 "ERROR: beh: Direct output into a file not supported.\n");
261 exit (CUPS_BACKEND_FAILED);
262 }
263
264 backend_argv[0] = uri;
265 backend_argv[1] = argv[1];
266 backend_argv[2] = argv[2];
267 backend_argv[3] = argv[3];
268 backend_argv[4] = (argc == 6 ? "1" : argv[4]);
269 backend_argv[5] = argv[5];
270 backend_argv[6] = filename;
271 backend_argv[7] = NULL;
272
273 bytes = snprintf(backend_path, sizeof(backend_path),
274 "%s/backend/%s", cups_serverbin, scheme);
275 if (bytes < 0 || bytes >= sizeof(backend_path))
276 {
277 fprintf(stderr,
278 "ERROR: beh: Invalid scheme (\"%s\"), could not determing backend path.\n",
279 scheme);
280 exit (CUPS_BACKEND_FAILED);
281 }
282
283 /*
284 * Overwrite the device URI and run the actual backend...
285 */
286
287 setenv("DEVICE_URI", uri, 1);
288
289 fprintf(stderr,
290 "DEBUG: beh: Executing backend command line \"%s '%s' '%s' '%s' '%s' '%s'%s%s\"...\n",
291 backend_path, backend_argv[1], backend_argv[2], backend_argv[3],
292 backend_argv[4], backend_argv[5],
293 (backend_argv[6] && backend_argv[6][0] ? " " : ""),
294 (backend_argv[6] && backend_argv[6][0] ? backend_argv[6] : ""));
295 fprintf(stderr,
296 "DEBUG: beh: Using device URI: %s\n",
297 uri);
298
299 if ((pid = fork()) == 0)
300 {
301 retval = execv(backend_path, backend_argv);
302 if (retval == -1)
303 fprintf(stderr, "ERROR: Unable to execute backend: %s\n",
304 strerror(errno));
305 exit (CUPS_BACKEND_FAILED);
306 }
307 else if (pid < 0)
308 {
309 fprintf(stderr, "ERROR: Unable to fork for backend\n");
310 return (CUPS_BACKEND_FAILED);
311 }
312
313 while ((wait_pid = wait(&wait_status)) < 0 && errno == EINTR);
314
315 if (wait_pid >= 0 && wait_status)
316 {
317 if (WIFEXITED(wait_status))
318 retval = WEXITSTATUS(wait_status);
319 else if (WTERMSIG(wait_status) != SIGTERM)
320 retval = WTERMSIG(wait_status);
321 else
322 retval = 0;
323 }
324
325 return (retval);
326 }
327
328
329 /*
330 * 'sigterm_handler()' - Handle termination signals.
331 */
332
333 static void
sigterm_handler(int sig)334 sigterm_handler(int sig) { /* I - Signal number (unused) */
335 (void)sig;
336
337 const char * const msg = "DEBUG: beh: Job canceled.\n";
338 // The if() is to eliminate the return value and silence the warning
339 // about an unused return value.
340 if (write(2, msg, strlen(msg)));
341
342 if (job_canceled)
343 _exit(CUPS_BACKEND_OK);
344 else
345 job_canceled = 1;
346 }
347