1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 * Copyright (C) 2016 Mopria Alliance, Inc.
4 * Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23
24 #ifndef _GNU_SOURCE
25 #define _GNU_SOURCE
26 #endif
27 #ifndef __USE_UNIX98
28 #define __USE_UNIX98
29 #endif
30
31 #include <pthread.h>
32
33 #include <semaphore.h>
34 #include <printer_capabilities_types.h>
35
36 #include "ifc_print_job.h"
37 #include "wprint_debug.h"
38 #include "plugin_db.h"
39
40 #include "ifc_status_monitor.h"
41
42 #include "ippstatus_monitor.h"
43 #include "ippstatus_capabilities.h"
44 #include "ipp_print.h"
45 #include "ipphelper.h"
46
47 #include "lib_printable_area.h"
48 #include "wprint_io_plugin.h"
49 #include "../plugins/media.h"
50
51 #define TAG "lib_wprint"
52
53 /* As expected by target devices */
54 #define USERAGENT_PREFIX "wPrintAndroid"
55
56 #define USE_PWG_OVER_PCLM 0
57
58 #if (USE_PWG_OVER_PCLM != 0)
59 #define _DEFAULT_PRINT_FORMAT PRINT_FORMAT_PWG
60 #define _DEFAULT_PCL_TYPE PCLPWG
61 #else // (USE_PWG_OVER_PCLM != 0)
62 #define _DEFAULT_PRINT_FORMAT PRINT_FORMAT_PCLM
63 #define _DEFAULT_PCL_TYPE PCLm
64 #endif // (USE_PWG_OVER_PCLM != 0)
65
66 #define _MAX_SPOOLED_JOBS 100
67 #define _MAX_MSGS (_MAX_SPOOLED_JOBS * 5)
68
69 #define _MAX_PAGES_PER_JOB 1000
70
71 #define MAX_IDLE_WAIT (5 * 60)
72
73 #define DEFAULT_RESOLUTION (300)
74
75 // When searching for a supported resolution this is the max resolution we will consider.
76 #define MAX_SUPPORTED_RESOLUTION (720)
77
78 #define MAX_DONE_WAIT (5 * 60)
79 #define MAX_START_WAIT (45)
80
81 #define IO_PORT_FILE 0
82
83 /*
84 * The following macros allow for up to 8 bits (256) for spooled job id#s and
85 * 24 bits (16 million) of a running sequence number to provide a reasonably
86 * unique job handle
87 */
88
89 // _ENCODE_HANDLE() is only called from _get_handle()
90 #define _ENCODE_HANDLE(X) ( (((++_running_number) & 0xffffff) << 8) | ((X) & 0xff) )
91 #define _DECODE_HANDLE(X) ((X) & 0xff)
92
93 #undef snprintf
94 #undef vsnprintf
95
96 typedef enum {
97 JOB_STATE_FREE, // queue element free
98 JOB_STATE_QUEUED, // job queued and waiting to be run
99 JOB_STATE_RUNNING, // job running (printing)
100 JOB_STATE_BLOCKED, // print job blocked due to printer stall/error
101 JOB_STATE_CANCEL_REQUEST, // print job cancelled by user,
102 JOB_STATE_CANCELLED, // print job cancelled by user, waiting to be freed
103 JOB_STATE_COMPLETED, // print job completed successfully, waiting to be freed
104 JOB_STATE_ERROR, // job could not be run due to error
105 JOB_STATE_CORRUPTED, // job could not be run due to error
106 JOB_STATE_BAD_CERTIFICATE, // server gave an unexpected ssl certificate
107 NUM_JOB_STATES
108 } _job_state_t;
109
110 typedef enum {
111 TOP_MARGIN = 0,
112 LEFT_MARGIN,
113 RIGHT_MARGIN,
114 BOTTOM_MARGIN,
115
116 NUM_PAGE_MARGINS
117 } _page_margins_t;
118
119 typedef enum {
120 MSG_RUN_JOB, MSG_QUIT,
121 } wprint_msg_t;
122
123 typedef struct {
124 wprint_msg_t id;
125 wJob_t job_id;
126 } _msg_t;
127
128 /*
129 * Define an entry in the job queue
130 */
131 typedef struct {
132 wJob_t job_handle;
133 _job_state_t job_state;
134 unsigned int blocked_reasons;
135 wprint_status_cb_t cb_fn;
136 char *printer_addr;
137 port_t port_num;
138 wprint_plugin_t *plugin;
139 ifc_print_job_t *print_ifc;
140 char *mime_type;
141 char *pathname;
142 bool is_dir;
143 bool last_page_seen;
144 int num_pages;
145 msg_q_id pageQ;
146 msg_q_id saveQ;
147
148 wprint_job_params_t job_params;
149 bool cancel_ok;
150 bool use_secure_uri;
151
152 const ifc_status_monitor_t *status_ifc;
153 char debug_path[MAX_PATHNAME_LENGTH + 1];
154 char printer_uri[1024];
155 int job_debug_fd;
156 int page_debug_fd;
157
158 /* A buffer of bytes containing the certificate received while setting up this job, if any. */
159 uint8 *certificate;
160 int certificate_len;
161 } _job_queue_t;
162
163 /*
164 * An entry for queued pages
165 */
166 typedef struct {
167 int page_num;
168 bool pdf_page;
169 bool last_page;
170 bool corrupted;
171 char filename[MAX_PATHNAME_LENGTH + 1];
172 unsigned int top_margin;
173 unsigned int left_margin;
174 unsigned int right_margin;
175 unsigned int bottom_margin;
176 } _page_t;
177
178 /*
179 * Entry for a registered plugin
180 */
181 typedef struct {
182 port_t port_num;
183 const wprint_io_plugin_t *io_plugin;
184 } _io_plugin_t;
185
186 static _job_queue_t _job_queue[_MAX_SPOOLED_JOBS];
187 static msg_q_id _msgQ;
188
189 static pthread_t _job_status_tid;
190 static pthread_t _job_tid;
191
192 static pthread_mutex_t _q_lock;
193 static pthread_mutexattr_t _q_lock_attr;
194
195 static sem_t _job_end_wait_sem;
196 static sem_t _job_start_wait_sem;
197
198 static _io_plugin_t _io_plugins[2];
199
200 static volatile bool stop_run = false;
201 static volatile bool stop_mutex = false;
202
203 static printer_capabilities_t g_printer_caps = {0};
204
205 char g_osName[MAX_ID_STRING_LENGTH + 1] = {0};
206 char g_appName[MAX_ID_STRING_LENGTH + 1] = {0};
207 char g_appVersion[MAX_ID_STRING_LENGTH + 1] = {0};
208
209 /*
210 * Convert a pcl_t type to a human-readable string
211 */
getPCLTypeString(pcl_t pclenum)212 static char *getPCLTypeString(pcl_t pclenum) {
213 switch (pclenum) {
214 case PCLNONE:
215 return "PCL_NONE";
216 case PCLm:
217 return "PCLm";
218 case PCLJPEG:
219 return "PCL_JPEG";
220 case PCLPWG:
221 return "PWG-Raster";
222 default:
223 return "unkonwn PCL Type";
224 }
225 }
226
227 /*
228 * Return a _job_queue_t item by its job_handle or NULL if not found.
229 */
_get_job_desc(wJob_t job_handle)230 static _job_queue_t *_get_job_desc(wJob_t job_handle) {
231 unsigned long index;
232 if (job_handle == WPRINT_BAD_JOB_HANDLE) {
233 return NULL;
234 }
235 index = _DECODE_HANDLE(job_handle);
236 if ((index < _MAX_SPOOLED_JOBS) && (_job_queue[index].job_handle == job_handle) &&
237 (_job_queue[index].job_state != JOB_STATE_FREE)) {
238 return (&_job_queue[index]);
239 } else {
240 return NULL;
241 }
242 }
243
244 /*
245 * Functions below to fill out the _debug_stream_ifc interface
246 */
247
_stream_dbg_end_job(wJob_t job_handle)248 static void _stream_dbg_end_job(wJob_t job_handle) {
249 _job_queue_t *jq = _get_job_desc(job_handle);
250 if (jq && (jq->job_debug_fd >= 0)) {
251 close(jq->job_debug_fd);
252 jq->job_debug_fd = -1;
253 }
254 }
255
_stream_dbg_start_job(wJob_t job_handle,const char * ext)256 static void _stream_dbg_start_job(wJob_t job_handle, const char *ext) {
257 _stream_dbg_end_job(job_handle);
258 _job_queue_t *jq = _get_job_desc(job_handle);
259 if (jq && jq->debug_path[0]) {
260 char filename[MAX_PATHNAME_LENGTH + 1];
261 snprintf(filename, MAX_PATHNAME_LENGTH, "%s/jobstream.%s", jq->debug_path, ext);
262 filename[MAX_PATHNAME_LENGTH] = 0;
263 jq->job_debug_fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
264 }
265 }
266
_stream_dbg_job_data(wJob_t job_handle,const unsigned char * buff,unsigned long nbytes)267 static void _stream_dbg_job_data(wJob_t job_handle, const unsigned char *buff,
268 unsigned long nbytes) {
269 _job_queue_t *jq = _get_job_desc(job_handle);
270 ssize_t bytes_written;
271 if (jq && (jq->job_debug_fd >= 0)) {
272 while (nbytes > 0) {
273 bytes_written = write(jq->job_debug_fd, buff, nbytes);
274 if (bytes_written < 0) {
275 return;
276 }
277 nbytes -= bytes_written;
278 buff += bytes_written;
279 }
280 }
281 }
282
_stream_dbg_end_page(wJob_t job_handle)283 static void _stream_dbg_end_page(wJob_t job_handle) {
284 _job_queue_t *jq = _get_job_desc(job_handle);
285 if (jq && (jq->page_debug_fd >= 0)) {
286 close(jq->page_debug_fd);
287 jq->page_debug_fd = -1;
288 }
289 }
290
_stream_dbg_page_data(wJob_t job_handle,const unsigned char * buff,unsigned long nbytes)291 static void _stream_dbg_page_data(wJob_t job_handle, const unsigned char *buff,
292 unsigned long nbytes) {
293 _job_queue_t *jq = _get_job_desc(job_handle);
294 ssize_t bytes_written;
295 if (jq && (jq->page_debug_fd >= 0)) {
296 while (nbytes > 0) {
297 bytes_written = write(jq->page_debug_fd, buff, nbytes);
298 if (bytes_written < 0) {
299 return;
300 }
301 nbytes -= bytes_written;
302 buff += bytes_written;
303 }
304 }
305 }
306
307 #define PPM_IDENTIFIER "P6\n"
308 #define PPM_HEADER_LENGTH 128
309
_stream_dbg_start_page(wJob_t job_handle,int page_number,int width,int height)310 static void _stream_dbg_start_page(wJob_t job_handle, int page_number, int width, int height) {
311 _stream_dbg_end_page(job_handle);
312 _job_queue_t *jq = _get_job_desc(job_handle);
313 if (jq && jq->debug_path[0]) {
314 union {
315 char filename[MAX_PATHNAME_LENGTH + 1];
316 char ppm_header[PPM_HEADER_LENGTH + 1];
317 } buff;
318 snprintf(buff.filename, MAX_PATHNAME_LENGTH, "%s/page%4.4d.ppm", jq->debug_path,
319 page_number);
320 buff.filename[MAX_PATHNAME_LENGTH] = 0;
321 jq->page_debug_fd = open(buff.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
322 int length = snprintf(buff.ppm_header, sizeof(buff.ppm_header), "%s\n#%*c\n%d %d\n%d\n",
323 PPM_IDENTIFIER, 0, ' ', width, height, 255);
324 int padding = sizeof(buff.ppm_header) - length;
325 snprintf(buff.ppm_header, sizeof(buff.ppm_header), "%s\n#%*c\n%d %d\n%d\n",
326 PPM_IDENTIFIER, padding, ' ', width, height, 255);
327 _stream_dbg_page_data(job_handle, (const unsigned char *) buff.ppm_header,
328 PPM_HEADER_LENGTH);
329 }
330 }
331
332 static const ifc_wprint_debug_stream_t _debug_stream_ifc = {
333 .debug_start_job = _stream_dbg_start_job, .debug_job_data = _stream_dbg_job_data,
334 .debug_end_job = _stream_dbg_end_job, .debug_start_page = _stream_dbg_start_page,
335 .debug_page_data = _stream_dbg_page_data, .debug_end_page = _stream_dbg_end_page
336 };
337
338 /*
339 * Return the debug stream interface corresponding to the specified job handle
340 */
getDebugStreamIfc(wJob_t handle)341 const ifc_wprint_debug_stream_t *getDebugStreamIfc(wJob_t handle) {
342 _job_queue_t *jq = _get_job_desc(handle);
343 if (jq) {
344 return (jq->debug_path[0] == 0) ? NULL : &_debug_stream_ifc;
345 }
346 return NULL;
347 }
348
349 const ifc_wprint_t _wprint_ifc = {
350 .msgQCreate = msgQCreate, .msgQDelete = msgQDelete,
351 .msgQSend = msgQSend, .msgQReceive = msgQReceive, .msgQNumMsgs = msgQNumMsgs,
352 .get_debug_stream_ifc = getDebugStreamIfc
353 };
354
355 static pcl_t _default_pcl_type = _DEFAULT_PCL_TYPE;
356
_printer_file_connect(const ifc_wprint_t * wprint_ifc)357 static const ifc_print_job_t *_printer_file_connect(const ifc_wprint_t *wprint_ifc) {
358 return printer_connect(IO_PORT_FILE);
359 }
360
_get_caps_ifc(port_t port_num)361 static const ifc_printer_capabilities_t *_get_caps_ifc(port_t port_num) {
362 int i;
363 for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) {
364 if (_io_plugins[i].port_num == port_num) {
365 if (_io_plugins[i].io_plugin == NULL) {
366 return NULL;
367 }
368 if (_io_plugins[i].io_plugin->getCapsIFC == NULL) {
369 return NULL;
370 } else {
371 return (_io_plugins[i].io_plugin->getCapsIFC(&_wprint_ifc));
372 }
373 }
374 }
375 return NULL;
376 }
377
_get_status_ifc(port_t port_num)378 static const ifc_status_monitor_t *_get_status_ifc(port_t port_num) {
379 int i;
380 for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) {
381 if (_io_plugins[i].port_num == port_num) {
382 if (_io_plugins[i].io_plugin == NULL) {
383 return NULL;
384 }
385 if (_io_plugins[i].io_plugin->getStatusIFC == NULL) {
386 return NULL;
387 } else {
388 return (_io_plugins[i].io_plugin->getStatusIFC(&_wprint_ifc));
389 }
390 }
391 }
392 return NULL;
393 }
394
_get_print_ifc(port_t port_num)395 static const ifc_print_job_t *_get_print_ifc(port_t port_num) {
396 int i;
397 for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) {
398 if (_io_plugins[i].port_num == port_num) {
399 if (_io_plugins[i].io_plugin == NULL) {
400 return NULL;
401 }
402 if (_io_plugins[i].io_plugin->getPrintIFC == NULL) {
403 return NULL;
404 } else {
405 return (_io_plugins[i].io_plugin->getPrintIFC(&_wprint_ifc));
406 }
407 }
408 }
409 return NULL;
410 }
411
412 /*
413 * Lock the semaphore for this module
414 */
_lock(void)415 static void _lock(void) {
416 if (!stop_mutex) {
417 pthread_mutex_lock(&_q_lock);
418 }
419 }
420
421 /*
422 * Unlock the semaphore for this module
423 */
_unlock(void)424 static void _unlock(void) {
425 if (!stop_mutex) {
426 pthread_mutex_unlock(&_q_lock);
427 }
428 }
429
_get_handle(void)430 static wJob_t _get_handle(void) {
431 static unsigned long _running_number = 0;
432 wJob_t job_handle = WPRINT_BAD_JOB_HANDLE;
433 int i, index, size;
434 char *ptr;
435
436 for (i = 0; i < _MAX_SPOOLED_JOBS; i++) {
437 index = (i + _running_number) % _MAX_SPOOLED_JOBS;
438
439 if (_job_queue[index].job_state == JOB_STATE_FREE) {
440 size = MAX_MIME_LENGTH + MAX_PRINTER_ADDR_LENGTH + MAX_PATHNAME_LENGTH + 4;
441 ptr = malloc(size);
442 if (ptr) {
443 memset(&_job_queue[index], 0, sizeof(_job_queue_t));
444 memset(ptr, 0, size);
445
446 _job_queue[index].job_debug_fd = -1;
447 _job_queue[index].page_debug_fd = -1;
448 _job_queue[index].printer_addr = ptr;
449
450 ptr += (MAX_PRINTER_ADDR_LENGTH + 1);
451 _job_queue[index].mime_type = ptr;
452 ptr += (MAX_MIME_LENGTH + 1);
453 _job_queue[index].pathname = ptr;
454
455 _job_queue[index].job_state = JOB_STATE_QUEUED;
456 _job_queue[index].job_handle = _ENCODE_HANDLE(index);
457
458 job_handle = _job_queue[index].job_handle;
459 }
460 break;
461 }
462 }
463 return job_handle;
464 }
465
_recycle_handle(wJob_t job_handle)466 static int _recycle_handle(wJob_t job_handle) {
467 _job_queue_t *jq = _get_job_desc(job_handle);
468
469 if (jq == NULL) {
470 return ERROR;
471 } else if ((jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_ERROR) ||
472 (jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_COMPLETED)) {
473 if (jq->print_ifc != NULL) {
474 jq->print_ifc->destroy(jq->print_ifc);
475 }
476
477 jq->print_ifc = NULL;
478 if (jq->status_ifc != NULL) {
479 jq->status_ifc->destroy(jq->status_ifc);
480 }
481 jq->status_ifc = NULL;
482 if (jq->job_params.useragent != NULL) {
483 free((void *) jq->job_params.useragent);
484 }
485 if (jq->job_params.certificate != NULL) {
486 free((void *) jq->job_params.certificate);
487 }
488 free(jq->printer_addr);
489 jq->job_state = JOB_STATE_FREE;
490 if (jq->job_debug_fd != -1) {
491 close(jq->job_debug_fd);
492 }
493 jq->job_debug_fd = -1;
494 if (jq->page_debug_fd != -1) {
495 close(jq->page_debug_fd);
496 }
497 jq->page_debug_fd = -1;
498 jq->debug_path[0] = 0;
499 if (jq->certificate) {
500 free(jq->certificate);
501 jq->certificate = NULL;
502 }
503 return OK;
504 } else {
505 return ERROR;
506 }
507 }
508
509 /*
510 * Stops the job status thread if it exists
511 */
_stop_status_thread(_job_queue_t * jq)512 static int _stop_status_thread(_job_queue_t *jq) {
513 if (!pthread_equal(_job_status_tid, pthread_self()) && (jq && jq->status_ifc)) {
514 (jq->status_ifc->stop)(jq->status_ifc);
515 _unlock();
516 pthread_join(_job_status_tid, 0);
517 _lock();
518 _job_status_tid = pthread_self();
519 return OK;
520 } else {
521 return ERROR;
522 }
523 }
524
525 /*
526 * Helper function to send job status callbacks to wprintJNI
527 */
_send_status_callback(_job_queue_t * jq,wprint_job_callback_params_t cb_param,int state,unsigned int blocked_reasons,int job_done_result)528 static void _send_status_callback(_job_queue_t *jq, wprint_job_callback_params_t cb_param,
529 int state, unsigned int blocked_reasons, int job_done_result) {
530 if (jq->cb_fn) {
531 cb_param.id = WPRINT_CB_PARAM_JOB_STATE;
532 cb_param.param.state = state;
533 cb_param.blocked_reasons = blocked_reasons;
534 cb_param.job_done_result = job_done_result;
535 jq->cb_fn(jq->job_handle, (void *) &cb_param);
536 }
537 }
538
539 /*
540 * Handles a new status message from the printer. Based on the status of wprint and the printer,
541 * this function will start/end a job, send another page, or return blocking errors.
542 */
_job_status_callback(const printer_state_dyn_t * new_status,const printer_state_dyn_t * old_status,void * param)543 static void _job_status_callback(const printer_state_dyn_t *new_status,
544 const printer_state_dyn_t *old_status, void *param) {
545 wprint_job_callback_params_t cb_param;
546 _job_queue_t *jq = (_job_queue_t *) param;
547 unsigned int i, blocked_reasons;
548 print_status_t statusnew, statusold;
549
550 statusnew = new_status->printer_status & ~PRINTER_IDLE_BIT;
551 statusold = old_status->printer_status & ~PRINTER_IDLE_BIT;
552 cb_param.certificate = jq->certificate;
553 cb_param.certificate_len = jq->certificate_len;
554
555 LOGD("_job_status_callback(): current printer state: %d", statusnew);
556 blocked_reasons = 0;
557 for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
558 if (new_status->printer_reasons[i] == PRINT_STATUS_MAX_STATE) {
559 break;
560 }
561 LOGD("_job_status_callback(): blocking reason %d: %d", i, new_status->printer_reasons[i]);
562 blocked_reasons |= (1 << new_status->printer_reasons[i]);
563 }
564
565 switch (statusnew) {
566 case PRINT_STATUS_UNKNOWN:
567 if ((new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE)
568 || (new_status->printer_reasons[0] == PRINT_STATUS_UNKNOWN)) {
569 sem_post(&_job_start_wait_sem);
570 sem_post(&_job_end_wait_sem);
571 _lock();
572 if ((new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE)
573 && ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL))) {
574 jq->print_ifc->enable_timeout(jq->print_ifc, 1);
575 }
576 _unlock();
577 }
578 break;
579
580 case PRINT_STATUS_IDLE:
581 if ((statusold > PRINT_STATUS_IDLE) || (statusold == PRINT_STATUS_CANCELLED)) {
582 // Print is over but the job wasn't ended correctly
583 if (jq->is_dir && !jq->last_page_seen) {
584 wprintPage(jq->job_handle, jq->num_pages + 1, NULL, true, false, 0, 0, 0, 0);
585 }
586 sem_post(&_job_end_wait_sem);
587 }
588 break;
589
590 case PRINT_STATUS_PRINTING:
591 // print job is unblocked but job-id is not generated
592 if (new_status->job_id == -1) {
593 sem_post(&_job_start_wait_sem);
594 _lock();
595 if ((jq->job_state != JOB_STATE_RUNNING) ||
596 (jq->blocked_reasons != blocked_reasons)) {
597 jq->job_state = JOB_STATE_RUNNING;
598 jq->blocked_reasons = blocked_reasons;
599 _send_status_callback(jq, cb_param, JOB_RUNNING, jq->blocked_reasons, OK);
600 }
601 _unlock();
602 }
603 break;
604
605 /* all is well, handled in job status */
606 case PRINT_STATUS_CANCELLED:
607 /* all is well, handled in job status */
608 case PRINT_STATUS_UNABLE_TO_CONNECT:
609 break;
610
611 default:
612 // an error has occurred, report it back to the client
613 sem_post(&_job_start_wait_sem);
614 _lock();
615
616 if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->blocked_reasons != blocked_reasons)) {
617 jq->job_state = JOB_STATE_BLOCKED;
618 jq->blocked_reasons = blocked_reasons;
619 // print job is blocked at the initial stage and job-id is not generated
620 if (new_status->job_id == -1) {
621 _send_status_callback(jq, cb_param, JOB_BLOCKED, blocked_reasons, OK);
622 }
623 }
624 _unlock();
625 break;
626 }
627 }
628
629 /*
630 * Callback after getting the print job state
631 * TODO (b/312004304): _print_job_state_callback code call removed due to crash.
632 */
_print_job_state_callback(const job_state_dyn_t * new_state,void * param)633 static void _print_job_state_callback(const job_state_dyn_t *new_state, void *param) {
634 wprint_job_callback_params_t cb_param = {};
635 _job_queue_t *jq = (_job_queue_t *) param;
636 unsigned long long blocked_reasons = 0;
637 int i;
638
639 cb_param.certificate = jq->certificate;
640 cb_param.certificate_len = jq->certificate_len;
641
642 LOGI("_print_job_state_callback(): new state: %d", new_state->job_state);
643 for (i = 0; i <= IPP_JOB_STATE_REASON_MAX_VALUE; i++) {
644 if (new_state->job_state_reasons[i] == IPP_JOB_STATE_REASON_MAX_VALUE)
645 break;
646 LOGD("_print_job_state_callback(): blocking reason %d: %d", i,
647 new_state->job_state_reasons[i]);
648 blocked_reasons |= (LONG_ONE << new_state->job_state_reasons[i]);
649 }
650
651 switch (new_state->job_state) {
652 case IPP_JOB_STATE_UNABLE_TO_CONNECT:
653 sem_post(&_job_start_wait_sem);
654 _lock();
655 jq->job_state = JOB_STATE_ERROR;
656 jq->blocked_reasons = blocked_reasons;
657 _unlock();
658 sem_post(&_job_end_wait_sem);
659 break;
660
661 case IPP_JOB_STATE_UNKNOWN:
662 case IPP_JOB_STATE_PENDING:
663 case IPP_JOB_STATE_PENDING_HELD:
664 break;
665
666 case IPP_JOB_STATE_PROCESSING:
667 sem_post(&_job_start_wait_sem);
668 // clear errors
669 _lock();
670 if (jq->job_state != JOB_STATE_RUNNING) {
671 jq->job_state = JOB_STATE_RUNNING;
672 _send_status_callback(jq, cb_param, JOB_RUNNING, 0, OK);
673 }
674 _unlock();
675 break;
676
677 case IPP_JOB_STATE_PROCESSING_STOPPED:
678 if (jq->job_state == JOB_STATE_BLOCKED) {
679 _send_status_callback(jq, cb_param, JOB_BLOCKED, jq->blocked_reasons, OK);
680 }
681 break;
682
683 case IPP_JOB_STATE_CANCELED:
684 sem_post(&_job_start_wait_sem);
685 sem_post(&_job_end_wait_sem);
686 if ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL)) {
687 jq->print_ifc->enable_timeout(jq->print_ifc, 1);
688 }
689 _lock();
690 jq->job_params.cancelled = true;
691 jq->blocked_reasons = blocked_reasons;
692 _unlock();
693 break;
694
695 case IPP_JOB_STATE_ABORTED:
696 sem_post(&_job_start_wait_sem);
697 sem_post(&_job_end_wait_sem);
698 _lock();
699 jq->job_state = JOB_STATE_ERROR;
700 jq->blocked_reasons = blocked_reasons;
701 _unlock();
702 break;
703
704 case IPP_JOB_STATE_COMPLETED:
705 sem_post(&_job_end_wait_sem);
706 break;
707
708 default:
709 break;
710 }
711 }
712
_job_status_thread(void * param)713 static void *_job_status_thread(void *param) {
714 _job_queue_t *jq = (_job_queue_t *) param;
715 (jq->status_ifc->start)(jq->status_ifc, _job_status_callback, _print_job_state_callback, param);
716 return NULL;
717 }
718
_start_status_thread(_job_queue_t * jq)719 static int _start_status_thread(_job_queue_t *jq) {
720 sigset_t allsig, oldsig;
721 int result = ERROR;
722
723 if ((jq == NULL) || (jq->status_ifc == NULL)) {
724 return result;
725 }
726
727 result = OK;
728 sigfillset(&allsig);
729 #if CHECK_PTHREAD_SIGMASK_STATUS
730 result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
731 #else // else CHECK_PTHREAD_SIGMASK_STATUS
732 pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
733 #endif // CHECK_PTHREAD_SIGMASK_STATUS
734 if (result == OK) {
735 result = pthread_create(&_job_status_tid, 0, _job_status_thread, jq);
736 if ((result == ERROR) && (_job_status_tid != pthread_self())) {
737 #if USE_PTHREAD_CANCEL
738 pthread_cancel(_job_status_tid);
739 #else // else USE_PTHREAD_CANCEL
740 pthread_kill(_job_status_tid, SIGKILL);
741 #endif // USE_PTHREAD_CANCEL
742 _job_status_tid = pthread_self();
743 }
744 }
745
746 if (result == OK) {
747 sched_yield();
748 #if CHECK_PTHREAD_SIGMASK_STATUS
749 result = pthread_sigmask(SIG_SETMASK, &oldsig, 0);
750 #else // else CHECK_PTHREAD_SIGMASK_STATUS
751 pthread_sigmask(SIG_SETMASK, &oldsig, 0);
752 #endif // CHECK_PTHREAD_SIGMASK_STATUS
753 }
754 return result;
755 }
756
757 /*
758 * Return true unless the server gave an unexpected certificate
759 */
_is_certificate_allowed(_job_queue_t * jq)760 static bool _is_certificate_allowed(_job_queue_t *jq) {
761 int result = true;
762
763 // Compare certificates if both are known
764 if (jq->job_params.certificate && jq->certificate) {
765 if (jq->job_params.certificate_len != jq->certificate_len) {
766 LOGD("_is_certificate_allowed: certificate length mismatch allowed=%d, received=%d",
767 jq->job_params.certificate_len, jq->certificate_len);
768 result = false;
769 } else if (0 != memcmp(jq->job_params.certificate, jq->certificate, jq->certificate_len)) {
770 LOGD("_is_certificate_allowed: certificate content mismatch");
771 result = false;
772 } else {
773 LOGD("_is_certificate_allowed: certificate match, len=%d",
774 jq->job_params.certificate_len);
775 }
776 }
777
778 return result;
779 }
780
781 /*
782 * Callback from lower layers containing certificate data, if any.
783 */
_validate_certificate(wprint_connect_info_t * connect_info,uint8 * data,int data_len)784 static int _validate_certificate(wprint_connect_info_t *connect_info, uint8 *data, int data_len) {
785 _job_queue_t *jq = connect_info->user;
786 LOGD("_validate_certificate: %s://%s:%d%s handling server cert len=%d for job %ld",
787 connect_info->uri_scheme, connect_info->printer_addr, connect_info->port_num,
788 connect_info->uri_path, data_len, jq->job_handle);
789
790 // Free any old certificate we have and save new certificate data
791 if (jq->certificate) {
792 free(jq->certificate);
793 jq->certificate = NULL;
794 }
795 jq->certificate = (uint8 *)malloc(data_len);
796 int error = 0;
797 if (jq->certificate == NULL) {
798 LOGD("_validate_certificate: malloc failed");
799 error = -1;
800 } else {
801 memcpy(jq->certificate, data, data_len);
802 jq->certificate_len = data_len;
803 if (!_is_certificate_allowed(jq)) {
804 LOGD("_validate_certificate: received certificate disallowed.");
805 error = -1;
806 }
807 }
808 return error;
809 }
810
811 /*
812 * Initialize the status interface (so we can use it to query for printer status.
813 */
_initialize_status_ifc(_job_queue_t * jq)814 static void _initialize_status_ifc(_job_queue_t *jq) {
815 wprint_connect_info_t connect_info;
816 connect_info.printer_addr = jq->printer_addr;
817 connect_info.uri_path = jq->printer_uri;
818 connect_info.port_num = jq->port_num;
819 if (jq->use_secure_uri) {
820 connect_info.uri_scheme = IPPS_PREFIX;
821 connect_info.user = jq;
822 connect_info.validate_certificate = _validate_certificate;
823 } else {
824 connect_info.uri_scheme = IPP_PREFIX;
825 connect_info.validate_certificate = NULL;
826 }
827 connect_info.timeout = DEFAULT_IPP_TIMEOUT;
828
829 // Initialize the status interface with this connection info
830 jq->status_ifc->init(jq->status_ifc, &connect_info);
831 }
832
833 /*
834 * Runs a print job. Contains logic for what to do given different printer statuses.
835 */
_job_thread(void * param)836 static void *_job_thread(void *param) {
837 wprint_job_callback_params_t cb_param = {};
838 _msg_t msg;
839 wJob_t job_handle;
840 _job_queue_t *jq;
841 _page_t page;
842 int i;
843 status_t job_result;
844 int corrupted = 0;
845 printer_capabilities_t printer_caps;
846
847 while (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), WAIT_FOREVER)) {
848 if (msg.id == MSG_RUN_JOB) {
849 LOGI("_job_thread(): Received message: MSG_RUN_JOB");
850 } else {
851 LOGI("_job_thread(): Received message: MSG_QUIT");
852 }
853
854 if (msg.id == MSG_QUIT) {
855 break;
856 }
857
858 job_handle = msg.job_id;
859
860 // check if this is a valid job_handle that is still active
861 _lock();
862
863 jq = _get_job_desc(job_handle);
864
865 // set state to running and invoke the plugin, there is one
866 if (jq) {
867 if (jq->job_state != JOB_STATE_QUEUED) {
868 _unlock();
869 continue;
870 }
871 corrupted = 0;
872 job_result = OK;
873 jq->job_params.plugin_data = NULL;
874
875 // clear out the semaphore just in case
876 while (sem_trywait(&_job_start_wait_sem) == OK) {
877 }
878 while (sem_trywait(&_job_end_wait_sem) == OK) {
879 }
880
881 // initialize the status ifc
882 if (jq->status_ifc != NULL) {
883 _initialize_status_ifc(jq);
884 }
885 // wait for the printer to be idle
886 if ((jq->status_ifc != NULL) && (jq->status_ifc->get_status != NULL)) {
887 int retry = 0;
888 bool idle = false;
889 bool bad_certificate = false;
890 printer_state_dyn_t printer_state;
891 while (!idle && !stop_run) {
892 print_status_t status;
893 jq->status_ifc->get_status(jq->status_ifc, &printer_state);
894 status = printer_state.printer_status & ~PRINTER_IDLE_BIT;
895
896 // Pass along any certificate received in future callbacks
897 cb_param.certificate = jq->certificate;
898 cb_param.certificate_len = jq->certificate_len;
899
900 // Presume we found an idle state
901 idle = true;
902 if (status == PRINT_STATUS_IDLE) {
903 printer_state.printer_status = PRINT_STATUS_IDLE;
904 jq->blocked_reasons = 0;
905 } else if (status == PRINT_STATUS_UNKNOWN
906 && printer_state.printer_reasons[0] == PRINT_STATUS_UNKNOWN) {
907 // no status available, break out and hope for the best
908 printer_state.printer_status = PRINT_STATUS_IDLE;
909 } else if ((status == PRINT_STATUS_UNKNOWN || status == PRINT_STATUS_SVC_REQUEST)
910 && ((printer_state.printer_reasons[0] == PRINT_STATUS_UNABLE_TO_CONNECT)
911 || (printer_state.printer_reasons[0] == PRINT_STATUS_OFFLINE))) {
912 if (_is_certificate_allowed(jq)) {
913 LOGD("%s: Received an Unable to Connect message", __func__);
914 jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
915 } else {
916 LOGD("%s: Bad certificate", __func__);
917 bad_certificate = true;
918 }
919 } else if (printer_state.printer_status & PRINTER_IDLE_BIT) {
920 LOGD("%s: printer blocked but appears to be in an idle state. "
921 "Allowing job to proceed", __func__);
922 printer_state.printer_status = PRINT_STATUS_IDLE;
923 } else if (retry >= MAX_IDLE_WAIT) {
924 jq->blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY;
925 } else if (!jq->job_params.cancelled) {
926 // Printer still appears busy, so stay in loop, notify, and poll again.
927 idle = false;
928 int blocked_reasons = 0;
929 for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) {
930 if (printer_state.printer_reasons[i] == PRINT_STATUS_MAX_STATE) {
931 break;
932 }
933 blocked_reasons |= (LONG_ONE << printer_state.printer_reasons[i]);
934 }
935 if (blocked_reasons == 0) {
936 blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY;
937 }
938
939 if ((jq->job_state != JOB_STATE_BLOCKED)
940 || (jq->blocked_reasons != blocked_reasons)) {
941 jq->job_state = JOB_STATE_BLOCKED;
942 jq->blocked_reasons = blocked_reasons;
943 _send_status_callback(jq, cb_param, JOB_BLOCKED, blocked_reasons, OK);
944 }
945 _unlock();
946 sleep(1);
947 _lock();
948 retry++;
949 }
950 }
951
952 if (jq->job_params.cancelled) {
953 job_result = CANCELLED;
954 } else if (bad_certificate) {
955 job_result = BAD_CERTIFICATE;
956 } else {
957 if (stop_run) {
958 jq->job_state = JOB_STATE_ERROR;
959 job_result = ERROR;
960 } else {
961 job_result = (((printer_state.printer_status & ~PRINTER_IDLE_BIT) ==
962 PRINT_STATUS_IDLE) ? OK : ERROR);
963 }
964 }
965 }
966
967 if (job_result == OK) {
968 if (jq->print_ifc) {
969 job_result = jq->print_ifc->init(jq->print_ifc, jq->printer_addr,
970 jq->port_num, jq->printer_uri, jq->use_secure_uri);
971 if (job_result == ERROR) {
972 jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT;
973 }
974 }
975 }
976
977 /* call the plugin's start_job method, if no other job is running
978 use callback to notify the client */
979
980 if (job_result == OK) {
981 _send_status_callback(jq, cb_param, JOB_RUNNING, 0, OK);
982 }
983
984 memcpy(&printer_caps, &g_printer_caps, sizeof(printer_capabilities_t));
985
986 jq->job_params.page_num = -1;
987 if (job_result == OK) {
988 if (jq->print_ifc != NULL) {
989 LOGD("_job_thread: Calling validate_job");
990 if (jq->print_ifc->validate_job != NULL) {
991 job_result = jq->print_ifc->validate_job(jq->print_ifc, &jq->job_params,
992 &printer_caps);
993 }
994 if (!_is_certificate_allowed(jq)) {
995 LOGD("_job_thread: bad certificate found at validate job");
996 job_result = BAD_CERTIFICATE;
997 }
998 /* PDF format plugin's start_job and end_job are to be called for each copy,
999 * inside the for-loop for num_copies.
1000 */
1001 if (job_result == OK) {
1002 _job_status_tid = pthread_self();
1003 _start_status_thread(jq);
1004 }
1005
1006 // Do not call start_job unless validate_job returned OK
1007 if ((job_result == OK) && (jq->print_ifc->start_job != NULL) &&
1008 (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0)) {
1009 jq->print_ifc->start_job(jq->print_ifc, &jq->job_params, &printer_caps);
1010 }
1011 }
1012
1013 // Do not call start_job unless validate_job returned OK
1014 if (job_result == OK && jq->plugin->start_job != NULL) {
1015 job_result = jq->plugin->start_job(job_handle, (void *) &_wprint_ifc,
1016 (void *) jq->print_ifc, &(jq->job_params));
1017 }
1018 }
1019
1020 if (job_result == OK) {
1021 jq->job_params.page_num = 0;
1022 }
1023
1024 // multi-page print job
1025 if (jq->is_dir && (job_result == OK)) {
1026 int per_copy_page_num;
1027 for (i = 0; (i < jq->job_params.num_copies) &&
1028 ((job_result == OK) || (job_result == CORRUPT)) &&
1029 (!jq->job_params.cancelled); i++) {
1030 if ((i > 0) &&
1031 jq->job_params.copies_supported &&
1032 (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) {
1033 LOGD("_job_thread multi_page: breaking out copies supported");
1034 break;
1035 }
1036 bool pdf_printed = false;
1037 if (jq->print_ifc->start_job != NULL &&
1038 (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) {
1039 jq->print_ifc->start_job(jq->print_ifc, &jq->job_params, &printer_caps);
1040 }
1041
1042 per_copy_page_num = 0;
1043 jq->job_state = JOB_STATE_RUNNING;
1044
1045 // while there is a page to print
1046 _unlock();
1047
1048 while (OK == msgQReceive(jq->pageQ, (char *) &page, sizeof(page),
1049 WAIT_FOREVER)) {
1050 _lock();
1051
1052 // check for any printing problems so far
1053 if (jq->print_ifc->check_status) {
1054 if (jq->print_ifc->check_status(jq->print_ifc) == ERROR) {
1055 job_result = ERROR;
1056 break;
1057 }
1058 }
1059
1060 /* take empty filename as cue to break out of the loop
1061 * but we have to do last_page processing
1062 */
1063
1064 // all copies are clubbed together as a single print job
1065 if (page.last_page && ((i == jq->job_params.num_copies - 1) ||
1066 (jq->job_params.copies_supported &&
1067 strcmp(jq->job_params.print_format,
1068 PRINT_FORMAT_PDF) == 0))) {
1069 jq->job_params.last_page = page.last_page;
1070 } else {
1071 jq->job_params.last_page = false;
1072 }
1073
1074 bool printBlankPage = (strcmp(jq->job_params.print_format,
1075 PRINT_FORMAT_PCLM) == 0) ? wprintBlankPageForPclm(
1076 &jq->job_params, &printer_caps) : wprintBlankPageForPwg(
1077 &jq->job_params, &printer_caps);
1078
1079 printBlankPage &= (jq->plugin->print_blank_page != NULL);
1080
1081 if (strlen(page.filename) > 0) {
1082 per_copy_page_num++;
1083 {
1084 jq->job_params.page_num++;
1085 }
1086 if (page.pdf_page) {
1087 jq->job_params.page_num = page.page_num;
1088 } else {
1089 jq->job_params.page_num = per_copy_page_num;
1090 }
1091
1092 // setup page margin information
1093 jq->job_params.print_top_margin += page.top_margin;
1094 jq->job_params.print_left_margin += page.left_margin;
1095 jq->job_params.print_right_margin += page.right_margin;
1096 jq->job_params.print_bottom_margin += page.bottom_margin;
1097
1098 jq->job_params.copy_num = (i + 1);
1099 jq->job_params.copy_page_num = page.page_num;
1100 jq->job_params.page_backside = !(page.page_num & 0x1);
1101 jq->job_params.page_corrupted = (page.corrupted ? 1 : 0);
1102 jq->job_params.page_printing = true;
1103 _unlock();
1104
1105 if (!page.corrupted) {
1106 LOGD("_job_thread(): page not corrupt, calling plugin's print_page"
1107 " function for page #%d", page.page_num);
1108
1109 // make sure we always print an even number of pages in duplex jobs
1110 if ((page.page_num == jq->job_params.job_pages_per_set) &&
1111 !(jq->job_params.face_down_tray) && printBlankPage) {
1112 jq->plugin->print_blank_page(job_handle, &(jq->job_params),
1113 jq->mime_type, page.filename);
1114 }
1115
1116 if (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0) {
1117 job_result = jq->plugin->print_page(&(jq->job_params),
1118 jq->mime_type,
1119 page.filename);
1120 } else if (!pdf_printed) {
1121 // for PDF plugin, print_page prints entire document,
1122 // so need to be called only once
1123 job_result = jq->plugin->print_page(&(jq->job_params),
1124 jq->mime_type,
1125 page.filename);
1126 pdf_printed = true;
1127 }
1128 } else {
1129 LOGD("_job_thread(): page IS corrupt, printing blank page for "
1130 "page #%d", page.page_num);
1131 job_result = CORRUPT;
1132 if ((jq->job_params.duplex != DUPLEX_MODE_NONE) &&
1133 (jq->plugin->print_blank_page != NULL)) {
1134 jq->plugin->print_blank_page(job_handle, &(jq->job_params),
1135 jq->mime_type, page.filename);
1136 }
1137 }
1138 _lock();
1139
1140 jq->job_params.print_top_margin -= page.top_margin;
1141 jq->job_params.print_left_margin -= page.left_margin;
1142 jq->job_params.print_right_margin -= page.right_margin;
1143 jq->job_params.print_bottom_margin -= page.bottom_margin;
1144 jq->job_params.page_printing = false;
1145
1146 // make sure we only count corrupted pages once
1147 if (page.corrupted == false) {
1148 page.corrupted = ((job_result == CORRUPT) ? true : false);
1149 corrupted += (job_result == CORRUPT);
1150 }
1151 }
1152
1153 // make sure we always print an even number of pages in duplex jobs
1154 if (page.last_page && (jq->job_params.face_down_tray) &&
1155 !(jq->job_params.page_backside) && printBlankPage) {
1156 _unlock();
1157 jq->plugin->print_blank_page(job_handle, &(jq->job_params),
1158 jq->mime_type, page.filename);
1159 _lock();
1160 }
1161
1162 // if multiple copies are requested, save the contents of the pageQ message
1163 if (jq->saveQ && !jq->job_params.cancelled && (job_result != ERROR)) {
1164 job_result = msgQSend(jq->saveQ, (char *) &page,
1165 sizeof(page), NO_WAIT, MSG_Q_FIFO);
1166
1167 // swap pageQ and saveQ
1168 if (page.last_page && !jq->job_params.last_page) {
1169 msg_q_id tmpQ = jq->pageQ;
1170 jq->pageQ = jq->saveQ;
1171 jq->saveQ = tmpQ;
1172
1173 // defensive programming
1174 while (msgQNumMsgs(tmpQ) > 0) {
1175 msgQReceive(tmpQ, (char *) &page, sizeof(page), NO_WAIT);
1176 LOGE("pageQ inconsistencies, discarding page #%d, file %s",
1177 page.page_num, page.filename);
1178 }
1179 }
1180 }
1181
1182 if (page.last_page || jq->job_params.cancelled) {
1183 // Leave the sempahore locked
1184 break;
1185 }
1186
1187 // unlock to go back to the top of the while loop
1188 _unlock();
1189 } // while there is another page
1190
1191 if ((strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0) &&
1192 (jq->print_ifc->end_job)) {
1193 int end_job_result = jq->print_ifc->end_job(jq->print_ifc);
1194 if (job_result == OK) {
1195 if (end_job_result == ERROR) {
1196 job_result = ERROR;
1197 } else if (end_job_result == CANCELLED) {
1198 job_result = CANCELLED;
1199 }
1200 }
1201 }
1202 } // for each copy of the job
1203 } else if (job_result == OK) {
1204 // single page job
1205 for (i = 0; ((i < jq->job_params.num_copies) && (job_result == OK)); i++) {
1206 if ((i > 0) && jq->job_params.copies_supported &&
1207 (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) {
1208 LOGD("_job_thread single_page: breaking out copies supported");
1209 break;
1210 }
1211
1212 // check for any printing problems so far
1213 if ((jq->print_ifc != NULL) && (jq->print_ifc->check_status)) {
1214 if (jq->print_ifc->check_status(jq->print_ifc) == ERROR) {
1215 job_result = ERROR;
1216 break;
1217 }
1218 }
1219
1220 jq->job_state = JOB_STATE_RUNNING;
1221 jq->job_params.page_num++;
1222 jq->job_params.last_page = (i == (jq->job_params.num_copies - 1));
1223 jq->job_params.copy_num = (i + 1);
1224 jq->job_params.copy_page_num = 1;
1225 jq->job_params.page_corrupted = (job_result == CORRUPT);
1226 jq->job_params.page_printing = true;
1227
1228 _unlock();
1229 job_result = jq->plugin->print_page(&(jq->job_params), jq->mime_type,
1230 jq->pathname);
1231
1232 if ((jq->job_params.duplex != DUPLEX_MODE_NONE)
1233 && (jq->plugin->print_blank_page != NULL)) {
1234 jq->plugin->print_blank_page(job_handle,
1235 &(jq->job_params), jq->mime_type, page.filename);
1236 }
1237
1238 _lock();
1239 jq->job_params.page_printing = false;
1240
1241 corrupted += (job_result == CORRUPT);
1242 } // for each copy
1243 }
1244
1245 // if we started the job end it
1246 if (jq->job_params.page_num >= 0) {
1247 // if the job was cancelled without sending anything through, print a blank sheet
1248 if ((jq->job_params.page_num == 0) && (jq->plugin->print_blank_page != NULL)) {
1249 jq->plugin->print_blank_page(job_handle, &(jq->job_params), jq->mime_type,
1250 page.filename);
1251 }
1252 if (jq->plugin->end_job != NULL) {
1253 jq->plugin->end_job(&(jq->job_params));
1254 }
1255 if ((jq->print_ifc != NULL) && (jq->print_ifc->end_job) &&
1256 (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0)) {
1257 _unlock();
1258 int end_job_result = jq->print_ifc->end_job(jq->print_ifc);
1259 if (job_result == OK) {
1260 if (end_job_result == ERROR) {
1261 job_result = ERROR;
1262 } else if (end_job_result == CANCELLED) {
1263 job_result = CANCELLED;
1264 }
1265 }
1266 _lock();
1267 }
1268 }
1269
1270 // if we started to print, wait for idle
1271 if ((jq->job_params.page_num > 0) && (jq->status_ifc != NULL)) {
1272 int retry, result;
1273 _unlock();
1274
1275 for (retry = 0, result = ERROR; ((result == ERROR) && (retry <= MAX_START_WAIT));
1276 retry++) {
1277 if (retry != 0) {
1278 sleep(1);
1279 }
1280 result = sem_trywait(&_job_start_wait_sem);
1281 }
1282
1283 if (result == OK) {
1284 for (retry = 0, result = ERROR; ((result == ERROR) && (retry <= MAX_DONE_WAIT));
1285 retry++) {
1286 if (retry != 0) {
1287 _lock();
1288 if (jq->job_params.cancelled && !jq->cancel_ok) {
1289 /* The user tried to cancel and it either didn't go through
1290 * or the printer doesn't support cancel through an OID.
1291 * Either way it's pointless to sit here waiting for idle when
1292 * may never come, so we'll bail out early
1293 */
1294 retry = (MAX_DONE_WAIT + 1);
1295 }
1296 _unlock();
1297 sleep(1);
1298 if (retry == MAX_DONE_WAIT) {
1299 _lock();
1300 if (!jq->job_params.cancelled &&
1301 (jq->blocked_reasons
1302 & (BLOCKED_REASON_OUT_OF_PAPER
1303 | BLOCKED_REASON_JAMMED
1304 | BLOCKED_REASON_DOOR_OPEN))) {
1305 retry = (MAX_DONE_WAIT - 1);
1306 }
1307 _unlock();
1308 }
1309 }
1310 result = sem_trywait(&_job_end_wait_sem);
1311 }
1312 } else {
1313 LOGD("_job_thread(): the job never started");
1314 }
1315 _lock();
1316 }
1317
1318 // make sure page_num doesn't stay as a negative number
1319 jq->job_params.page_num = MAX(0, jq->job_params.page_num);
1320 _stop_status_thread(jq);
1321
1322 if (corrupted != 0) {
1323 job_result = CORRUPT;
1324 }
1325
1326 LOGI("job_thread(): with job_state value: %d ", jq->job_state);
1327 if (jq->job_state == JOB_STATE_ERROR) {
1328 job_result = ERROR;
1329 LOGI("_job_thread(): job finished early: do not send callback again");
1330 } else {
1331 switch (job_result) {
1332 case OK:
1333 if (!jq->job_params.cancelled) {
1334 jq->job_state = JOB_STATE_COMPLETED;
1335 jq->blocked_reasons = 0;
1336 break;
1337 } else {
1338 job_result = CANCELLED;
1339 }
1340 case CANCELLED:
1341 jq->job_state = JOB_STATE_CANCELLED;
1342 jq->blocked_reasons = BLOCKED_REASONS_CANCELLED;
1343 if (!jq->cancel_ok) {
1344 jq->blocked_reasons |= BLOCKED_REASON_PARTIAL_CANCEL;
1345 }
1346 break;
1347 case CORRUPT:
1348 LOGE("_job_thread(): %d file(s) in the job were corrupted", corrupted);
1349 jq->job_state = JOB_STATE_CORRUPTED;
1350 jq->blocked_reasons = 0;
1351 break;
1352 case BAD_CERTIFICATE:
1353 LOGD("_job_thread(): BAD_CERTIFICATE");
1354 jq->job_state = JOB_STATE_BAD_CERTIFICATE;
1355 jq->blocked_reasons = 0;
1356 break;
1357 case ERROR:
1358 default:
1359 LOGE("_job_thread(): ERROR plugin->start_job(%ld): %s => %s", job_handle,
1360 jq->mime_type, jq->job_params.print_format);
1361 job_result = ERROR;
1362 jq->job_state = JOB_STATE_ERROR;
1363 break;
1364 } // job_result
1365 }
1366
1367 // end of job callback
1368 _send_status_callback(jq, cb_param, JOB_DONE, jq->blocked_reasons, job_result);
1369
1370 if (jq->print_ifc != NULL) {
1371 jq->print_ifc->destroy(jq->print_ifc);
1372 jq->print_ifc = NULL;
1373 }
1374
1375 if (jq->status_ifc != NULL) {
1376 jq->status_ifc->destroy(jq->status_ifc);
1377 jq->status_ifc = NULL;
1378 }
1379 } else {
1380 LOGI("_job_thread(): job %ld not in queue .. maybe cancelled", job_handle);
1381 }
1382
1383 _unlock();
1384 LOGI("_job_thread(): job finished: %ld", job_handle);
1385 }
1386
1387 sem_post(&_job_end_wait_sem);
1388 return NULL;
1389 }
1390
1391 /*
1392 * Starts the wprint background job thread
1393 */
_start_thread(void)1394 static int _start_thread(void) {
1395 sigset_t allsig, oldsig;
1396 int result;
1397
1398 _job_tid = pthread_self();
1399
1400 result = OK;
1401 stop_run = false;
1402 sigfillset(&allsig);
1403 #if CHECK_PTHREAD_SIGMASK_STATUS
1404 result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
1405 #else // else CHECK_PTHREAD_SIGMASK_STATUS
1406 pthread_sigmask(SIG_SETMASK, &allsig, &oldsig);
1407 #endif // CHECK_PTHREAD_SIGMASK_STATUS
1408 if (result == OK) {
1409 result = pthread_create(&_job_tid, 0, _job_thread, NULL);
1410 if ((result == ERROR) && (_job_tid != pthread_self())) {
1411 #if USE_PTHREAD_CANCEL
1412 pthread_cancel(_job_tid);
1413 #else // else USE_PTHREAD_CANCEL
1414 pthread_kill(_job_tid, SIGKILL);
1415 #endif // USE_PTHREAD_CANCEL
1416 _job_tid = pthread_self();
1417 }
1418 }
1419
1420 if (result == OK) {
1421 sched_yield();
1422 #if CHECK_PTHREAD_SIGMASK_STATUS
1423 result = pthread_sigmask(SIG_SETMASK, &oldsig, 0);
1424 #else // else CHECK_PTHREAD_SIGMASK_STATUS
1425 pthread_sigmask(SIG_SETMASK, &oldsig, 0);
1426 #endif // CHECK_PTHREAD_SIGMASK_STATUS
1427 }
1428
1429 return result;
1430 }
1431
1432 /*
1433 * Waits for the job thread to reach a stopped state
1434 */
_stop_thread(void)1435 static int _stop_thread(void) {
1436 stop_run = true;
1437 if (!pthread_equal(_job_tid, pthread_self())) {
1438 pthread_join(_job_tid, 0);
1439 _job_tid = pthread_self();
1440 return OK;
1441 } else {
1442 return ERROR;
1443 }
1444 }
1445
1446 static const wprint_io_plugin_t _file_io_plugin = {
1447 .version = WPRINT_PLUGIN_VERSION(_INTERFACE_MINOR_VERSION),
1448 .port_num = PORT_FILE, .getCapsIFC = NULL, .getStatusIFC = NULL,
1449 .getPrintIFC = _printer_file_connect,};
1450
1451 static const wprint_io_plugin_t _ipp_io_plugin = {
1452 .version = WPRINT_PLUGIN_VERSION(_INTERFACE_MINOR_VERSION),
1453 .port_num = PORT_IPP, .getCapsIFC = ipp_status_get_capabilities_ifc,
1454 .getStatusIFC = ipp_status_get_monitor_ifc, .getPrintIFC = ipp_get_print_ifc,};
1455
_setup_io_plugins()1456 static void _setup_io_plugins() {
1457 _io_plugins[0].port_num = PORT_FILE;
1458 _io_plugins[0].io_plugin = &_file_io_plugin;
1459
1460 _io_plugins[1].port_num = PORT_IPP;
1461 _io_plugins[1].io_plugin = &_ipp_io_plugin;
1462 }
1463
1464 extern wprint_plugin_t *libwprintplugin_pcl_reg(void);
1465
1466 extern wprint_plugin_t *libwprintplugin_pdf_reg(void);
1467
_setup_print_plugins()1468 static void _setup_print_plugins() {
1469 plugin_reset();
1470 plugin_add(libwprintplugin_pcl_reg());
1471 plugin_add(libwprintplugin_pdf_reg());
1472 }
1473
wprintIsRunning()1474 bool wprintIsRunning() {
1475 return _msgQ != 0;
1476 }
1477
wprintInit(void)1478 int wprintInit(void) {
1479 int count = 0;
1480
1481 _setup_print_plugins();
1482 _setup_io_plugins();
1483
1484 _msgQ = msgQCreate(_MAX_MSGS, sizeof(_msg_t));
1485
1486 if (!_msgQ) {
1487 LOGE("ERROR: cannot create msgQ");
1488 return ERROR;
1489 }
1490
1491 sem_init(&_job_end_wait_sem, 0, 0);
1492 sem_init(&_job_start_wait_sem, 0, 0);
1493
1494 signal(SIGPIPE, SIG_IGN); // avoid broken pipe process shutdowns
1495 pthread_mutexattr_settype(&_q_lock_attr, PTHREAD_MUTEX_RECURSIVE_NP);
1496 pthread_mutex_init(&_q_lock, &_q_lock_attr);
1497 stop_mutex = false;
1498
1499 if (_start_thread() != OK) {
1500 LOGE("could not start job thread");
1501 return ERROR;
1502 }
1503 return count;
1504 }
1505
1506 static const printer_capabilities_t _default_cap = {.color = true, .borderless = true,
1507 .numSupportedMediaSizes = 0, .numSupportedMediaTrays = 0,
1508 .numSupportedMediaTypes = 0,};
1509
1510 /*
1511 * Check if a media size is supported
1512 */
is_supported(media_size_t media_size)1513 static bool is_supported(media_size_t media_size) {
1514 int i;
1515 for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) {
1516 if (SupportedMediaSizes[i].media_size == media_size) return true;
1517 }
1518 return false;
1519 }
1520
1521 /*
1522 * Return true if the specified int array of the supplied length contains a value.
1523 */
int_array_contains(const int * array,int length,int value)1524 static bool int_array_contains(const int *array, int length, int value) {
1525 for (int i = 0; i < length; i++) {
1526 if (array[i] == value) return true;
1527 }
1528 return false;
1529 }
1530
1531 /*
1532 * Checks printers reported media sizes and validates that wprint supports them
1533 */
_validate_supported_media_sizes(printer_capabilities_t * printer_cap)1534 static void _validate_supported_media_sizes(printer_capabilities_t *printer_cap) {
1535 if (printer_cap == NULL) return;
1536
1537 if (printer_cap->numSupportedMediaSizes == 0) {
1538 unsigned int i = 0;
1539 printer_cap->supportedMediaSizes[i++] = ISO_A4;
1540 printer_cap->supportedMediaSizes[i++] = US_LETTER;
1541 printer_cap->supportedMediaSizes[i++] = INDEX_CARD_4X6;
1542 printer_cap->supportedMediaSizes[i++] = INDEX_CARD_5X7;
1543 printer_cap->numSupportedMediaSizes = i;
1544 } else {
1545 unsigned int read, write;
1546 for (read = write = 0; read < printer_cap->numSupportedMediaSizes; read++) {
1547 if (is_supported(printer_cap->supportedMediaSizes[read])) {
1548 printer_cap->supportedMediaSizes[write++] =
1549 printer_cap->supportedMediaSizes[read];
1550 }
1551 }
1552 printer_cap->numSupportedMediaSizes = write;
1553 }
1554 }
1555
1556 /*
1557 * Checks printers numSupportedMediaTrays. If none, then add Auto.
1558 */
_validate_supported_media_trays(printer_capabilities_t * printer_cap)1559 static void _validate_supported_media_trays(printer_capabilities_t *printer_cap) {
1560 if (printer_cap == NULL) return;
1561
1562 if (printer_cap->numSupportedMediaTrays == 0) {
1563 printer_cap->supportedMediaTrays[0] = TRAY_SRC_AUTO_SELECT;
1564 printer_cap->numSupportedMediaTrays = 1;
1565 }
1566 }
1567
1568 /*
1569 * Add a printer's supported input formats to the capabilities struct
1570 */
_collect_supported_input_formats(printer_capabilities_t * printer_caps)1571 static void _collect_supported_input_formats(printer_capabilities_t *printer_caps) {
1572 unsigned long long input_formats = 0;
1573 plugin_get_passthru_input_formats(&input_formats);
1574
1575 // remove things the printer can't support
1576 if (!printer_caps->canPrintPDF) {
1577 input_formats &= ~(1 << INPUT_MIME_TYPE_PDF);
1578 }
1579 if (!printer_caps->canPrintPCLm) {
1580 input_formats &= ~(1 << INPUT_MIME_TYPE_PCLM);
1581 }
1582 if (!printer_caps->canPrintPWG) {
1583 input_formats &= ~(1 << INPUT_MIME_TYPE_PWG);
1584 }
1585 printer_caps->supportedInputMimeTypes = input_formats;
1586 }
1587
1588 /*
1589 * Check the print resolutions supported by the printer and verify that wprint supports them.
1590 * If nothing is found, the desired resolution is selected.
1591 */
_findCloseResolutionSupported(int desiredResolution,int maxResolution,const printer_capabilities_t * printer_cap)1592 static unsigned int _findCloseResolutionSupported(int desiredResolution, int maxResolution,
1593 const printer_capabilities_t *printer_cap) {
1594 int closeResolution = 0;
1595 int closeDifference = 0;
1596 unsigned int index = 0;
1597 for (index = 0; index < printer_cap->numSupportedResolutions; index++) {
1598 int resolution = printer_cap->supportedResolutions[index];
1599 if (resolution == desiredResolution) {
1600 // An exact match wins.. stop looking.
1601 return resolution;
1602 } else {
1603 int difference = abs(desiredResolution - resolution);
1604 if ((closeResolution == 0) || (difference < closeDifference)) {
1605 if (resolution <= maxResolution) {
1606 // We found a better match now.. record it but keep looking.
1607 closeResolution = resolution;
1608 closeDifference = difference;
1609 }
1610 }
1611 }
1612 }
1613
1614 // If we get here we did not find an exact match.
1615 if (closeResolution == 0) {
1616 // We did not find anything.. just pick the desired value.
1617 closeResolution = desiredResolution;
1618 }
1619 return closeResolution;
1620 }
1621
wprintGetCapabilities(const wprint_connect_info_t * connect_info,printer_capabilities_t * printer_cap)1622 status_t wprintGetCapabilities(const wprint_connect_info_t *connect_info,
1623 printer_capabilities_t *printer_cap) {
1624 LOGD("wprintGetCapabilities: Enter");
1625 status_t result = ERROR;
1626 int index;
1627 int port_num = connect_info->port_num;
1628 const ifc_printer_capabilities_t *caps_ifc = NULL;
1629
1630 memcpy(printer_cap, &_default_cap, sizeof(printer_capabilities_t));
1631
1632 caps_ifc = _get_caps_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP));
1633 LOGD("wprintGetCapabilities: after getting caps ifc: %p", caps_ifc);
1634 switch (port_num) {
1635 case PORT_FILE:
1636 printer_cap->duplex = 1;
1637 printer_cap->borderless = 1;
1638 printer_cap->canPrintPCLm = (_default_pcl_type == PCLm);
1639 printer_cap->canPrintPWG = (_default_pcl_type == PCLPWG);
1640 printer_cap->stripHeight = STRIPE_HEIGHT;
1641 result = OK;
1642 break;
1643 default:
1644 break;
1645 }
1646
1647 if (caps_ifc != NULL) {
1648 caps_ifc->init(caps_ifc, connect_info);
1649 result = caps_ifc->get_capabilities(caps_ifc, printer_cap);
1650 caps_ifc->destroy(caps_ifc);
1651 }
1652
1653 _validate_supported_media_sizes(printer_cap);
1654 _collect_supported_input_formats(printer_cap);
1655 _validate_supported_media_trays(printer_cap);
1656
1657 printer_cap->isSupported = (printer_cap->canPrintPCLm || printer_cap->canPrintPDF ||
1658 printer_cap->canPrintPWG);
1659
1660 if (result == OK) {
1661 memcpy(&g_printer_caps, printer_cap, sizeof(printer_capabilities_t));
1662
1663 LOGD("\tmake: %s", printer_cap->make);
1664 LOGD("\thas color: %d", printer_cap->color);
1665 LOGD("\tcan duplex: %d", printer_cap->duplex);
1666 LOGD("\tcan rotate back page: %d", printer_cap->canRotateDuplexBackPage);
1667 LOGD("\tcan print borderless: %d", printer_cap->borderless);
1668 LOGD("\tcan print pdf: %d", printer_cap->canPrintPDF);
1669 LOGD("\tcan print pclm: %d", printer_cap->canPrintPCLm);
1670 LOGD("\tcan print pwg: %d", printer_cap->canPrintPWG);
1671 LOGD("\tsource application name supported: %d", printer_cap->docSourceAppName);
1672 LOGD("\tsource application version supported: %d", printer_cap->docSourceAppVersion);
1673 LOGD("\tsource os name supported: %d", printer_cap->docSourceOsName);
1674 LOGD("\tsource os version supported: %d", printer_cap->docSourceOsVersion);
1675 LOGD("\tprinter supported: %d", printer_cap->isSupported);
1676 LOGD("\tstrip height: %d", printer_cap->stripHeight);
1677 LOGD("\tinkjet: %d", printer_cap->inkjet);
1678 LOGD("\tresolutions supported:");
1679 for (index = 0; index < printer_cap->numSupportedResolutions; index++) {
1680 LOGD("\t (%d dpi)", printer_cap->supportedResolutions[index]);
1681 }
1682 }
1683 LOGD("wprintGetCapabilities: Exit");
1684 return result;
1685 }
1686
1687 /*
1688 * Returns a preferred print format supported by the printer
1689 */
_get_print_format(const char * mime_type,const wprint_job_params_t * job_params,const printer_capabilities_t * cap)1690 char *_get_print_format(const char *mime_type, const wprint_job_params_t *job_params,
1691 const printer_capabilities_t *cap) {
1692 char *print_format = NULL;
1693
1694 errno = OK;
1695
1696 if (((strcmp(mime_type, MIME_TYPE_PDF) == 0) && cap->canPrintPDF)) {
1697 // For content type=photo and a printer that supports both PCLm and PDF,
1698 // prefer PCLm over PDF.
1699 if (job_params && (strcasecmp(job_params->docCategory, "photo") == 0) &&
1700 cap->canPrintPCLm) {
1701 print_format = PRINT_FORMAT_PCLM;
1702 LOGI("_get_print_format(): print_format switched from PDF to PCLm");
1703 } else {
1704 print_format = PRINT_FORMAT_PDF;
1705 }
1706 } else if (cap->canPrintPCLm || cap->canPrintPDF) {
1707 // PCLm is a subset of PDF
1708 print_format = PRINT_FORMAT_PCLM;
1709 #if (USE_PWG_OVER_PCLM != 0)
1710 if (cap->canPrintPWG) {
1711 print_format = PRINT_FORMAT_PWG;
1712 }
1713 #endif // (USE_PWG_OVER_PCLM != 0)
1714 } else if (cap->canPrintPWG) {
1715 print_format = PRINT_FORMAT_PWG;
1716 } else {
1717 errno = EBADRQC;
1718 }
1719
1720 if (print_format != NULL) {
1721 LOGI("\t_get_print_format(): print_format: %s", print_format);
1722 }
1723
1724 return print_format;
1725 }
1726
wprintGetDefaultJobParams(wprint_job_params_t * job_params)1727 status_t wprintGetDefaultJobParams(wprint_job_params_t *job_params) {
1728 status_t result = ERROR;
1729 static const wprint_job_params_t _default_job_params = {.print_format = _DEFAULT_PRINT_FORMAT,
1730 .pcl_type = _DEFAULT_PCL_TYPE, .media_size = US_LETTER, .media_type = MEDIA_PLAIN,
1731 .duplex = DUPLEX_MODE_NONE, .dry_time = DUPLEX_DRY_TIME_NORMAL,
1732 .color_space = COLOR_SPACE_COLOR, .media_tray = TRAY_SRC_AUTO_SELECT,
1733 .pixel_units = DEFAULT_RESOLUTION, .render_flags = 0, .num_copies =1,
1734 .borderless = false, .cancelled = false, .face_down_tray = false,
1735 .ipp_1_0_supported = false, .ipp_2_0_supported = false, .epcl_ipp_supported = false,
1736 .strip_height = STRIPE_HEIGHT, .docCategory = {0},
1737 .copies_supported = false, .preserve_scaling = false};
1738
1739 if (job_params == NULL) return result;
1740
1741 memcpy(job_params, &_default_job_params, sizeof(_default_job_params));
1742
1743 return OK;
1744 }
1745
wprintGetFinalJobParams(wprint_job_params_t * job_params,const printer_capabilities_t * printer_cap)1746 status_t wprintGetFinalJobParams(wprint_job_params_t *job_params,
1747 const printer_capabilities_t *printer_cap) {
1748 int i;
1749 status_t result = ERROR;
1750 float margins[NUM_PAGE_MARGINS];
1751
1752 if (job_params == NULL) {
1753 return result;
1754 }
1755 result = OK;
1756
1757 job_params->accepts_pclm = printer_cap->canPrintPCLm;
1758 job_params->accepts_pdf = printer_cap->canPrintPDF;
1759 job_params->media_default = printer_cap->mediaDefault;
1760
1761 if (printer_cap->ePclIppVersion == 1) {
1762 job_params->epcl_ipp_supported = true;
1763 }
1764
1765 if (printer_cap->canCopy) {
1766 job_params->copies_supported = true;
1767 }
1768
1769 if (printer_cap->ippVersionMajor == 2) {
1770 job_params->ipp_1_0_supported = true;
1771 job_params->ipp_2_0_supported = true;
1772 } else if (printer_cap->ippVersionMajor == 1) {
1773 job_params->ipp_1_0_supported = true;
1774 job_params->ipp_2_0_supported = false;
1775 }
1776
1777 if (!printer_cap->color) {
1778 job_params->color_space = COLOR_SPACE_MONO;
1779 }
1780
1781 if (printer_cap->canPrintPCLm || printer_cap->canPrintPDF) {
1782 job_params->pcl_type = PCLm;
1783 #if (USE_PWG_OVER_PCLM != 0)
1784 if ( printer_cap->canPrintPWG) {
1785 job_params->pcl_type = PCLPWG;
1786 }
1787 #endif // (USE_PWG_OVER_PCLM != 0)
1788 } else if (printer_cap->canPrintPWG) {
1789 job_params->pcl_type = PCLPWG;
1790 }
1791
1792 LOGD("wprintGetFinalJobParams: Using PCL Type %s", getPCLTypeString(job_params->pcl_type));
1793
1794 // set strip height
1795 job_params->strip_height = printer_cap->stripHeight;
1796
1797 // make sure the number of copies is valid
1798 if (job_params->num_copies <= 0) {
1799 job_params->num_copies = 1;
1800 }
1801
1802 // If printing photo and HIGH quality is supported, specify it.
1803 if (strcasecmp(job_params->docCategory, "photo") == 0 && int_array_contains(
1804 printer_cap->supportedQuality, printer_cap->numSupportedQuality, IPP_QUALITY_HIGH)) {
1805 job_params->print_quality = IPP_QUALITY_HIGH;
1806 }
1807
1808 // confirm that the media size is supported
1809 for (i = 0; i < printer_cap->numSupportedMediaSizes; i++) {
1810 if (job_params->media_size == printer_cap->supportedMediaSizes[i]) {
1811 break;
1812 }
1813 }
1814
1815 if (i >= printer_cap->numSupportedMediaSizes) {
1816 job_params->media_size = ISO_A4;
1817 job_params->media_tray = TRAY_SRC_AUTO_SELECT;
1818 }
1819
1820 // check that we support the media tray
1821 for (i = 0; i < printer_cap->numSupportedMediaTrays; i++) {
1822 if (job_params->media_tray == printer_cap->supportedMediaTrays[i]) {
1823 break;
1824 }
1825 }
1826
1827 // media tray not supported, default to automatic
1828 if (i >= printer_cap->numSupportedMediaTrays) {
1829 job_params->media_tray = TRAY_SRC_AUTO_SELECT;
1830 }
1831
1832 if (printer_cap->isMediaSizeNameSupported == true) {
1833 job_params->media_size_name = true;
1834 } else {
1835 job_params->media_size_name = false;
1836 }
1837
1838 // verify borderless setting
1839 if ((job_params->borderless == true) && !printer_cap->borderless) {
1840 job_params->borderless = false;
1841 }
1842
1843 // borderless and margins don't get along
1844 if (job_params->borderless &&
1845 ((job_params->job_top_margin > 0.0f) || (job_params->job_left_margin > 0.0f) ||
1846 (job_params->job_right_margin > 0.0f)
1847 || (job_params->job_bottom_margin > 0.0f))) {
1848 job_params->borderless = false;
1849 }
1850
1851 // verify duplex setting
1852 if ((job_params->duplex != DUPLEX_MODE_NONE) && !printer_cap->duplex) {
1853 job_params->duplex = DUPLEX_MODE_NONE;
1854 }
1855
1856 // borderless and duplex don't get along either
1857 if (job_params->borderless && (job_params->duplex != DUPLEX_MODE_NONE)) {
1858 job_params->duplex = DUPLEX_MODE_NONE;
1859 }
1860
1861 if ((job_params->duplex == DUPLEX_MODE_BOOK)
1862 && !printer_cap->canRotateDuplexBackPage) {
1863 job_params->render_flags |= RENDER_FLAG_ROTATE_BACK_PAGE;
1864 }
1865
1866 if (job_params->render_flags & RENDER_FLAG_ROTATE_BACK_PAGE) {
1867 LOGD("wprintGetFinalJobParams: Duplex is on and device needs back page rotated.");
1868 }
1869
1870 job_params->face_down_tray = printer_cap->faceDownTray;
1871
1872 if (job_params->render_flags & RENDER_FLAG_AUTO_SCALE) {
1873 job_params->render_flags |= AUTO_SCALE_RENDER_FLAGS;
1874 } else if (job_params->render_flags & RENDER_FLAG_AUTO_FIT) {
1875 job_params->render_flags |= AUTO_FIT_RENDER_FLAGS;
1876 }
1877
1878 job_params->pixel_units = _findCloseResolutionSupported(DEFAULT_RESOLUTION,
1879 MAX_SUPPORTED_RESOLUTION, printer_cap);
1880
1881 printable_area_get_default_margins(job_params, printer_cap, &margins[TOP_MARGIN],
1882 &margins[LEFT_MARGIN], &margins[RIGHT_MARGIN], &margins[BOTTOM_MARGIN]);
1883 printable_area_get(job_params, margins[TOP_MARGIN], margins[LEFT_MARGIN],
1884 margins[RIGHT_MARGIN], margins[BOTTOM_MARGIN]);
1885
1886 job_params->accepts_app_name = printer_cap->docSourceAppName;
1887 job_params->accepts_app_version = printer_cap->docSourceAppVersion;
1888 job_params->accepts_os_name = printer_cap->docSourceOsName;
1889 job_params->accepts_os_version = printer_cap->docSourceOsVersion;
1890
1891 return result;
1892 }
1893
wprintStartJob(const char * printer_addr,port_t port_num,const wprint_job_params_t * job_params,const printer_capabilities_t * printer_cap,const char * mime_type,const char * pathname,wprint_status_cb_t cb_fn,const char * debugDir,const char * scheme)1894 wJob_t wprintStartJob(const char *printer_addr, port_t port_num,
1895 const wprint_job_params_t *job_params, const printer_capabilities_t *printer_cap,
1896 const char *mime_type, const char *pathname, wprint_status_cb_t cb_fn,
1897 const char *debugDir, const char *scheme) {
1898 wJob_t job_handle = WPRINT_BAD_JOB_HANDLE;
1899 _msg_t msg;
1900 struct stat stat_buf;
1901 bool is_dir = false;
1902 _job_queue_t *jq;
1903 wprint_plugin_t *plugin = NULL;
1904 char *print_format;
1905 ifc_print_job_t *print_ifc;
1906
1907 if (mime_type == NULL) {
1908 errno = EINVAL;
1909 return job_handle;
1910 }
1911
1912 print_format = _get_print_format(mime_type, job_params, printer_cap);
1913 if (print_format == NULL) return job_handle;
1914
1915 // check to see if we have an appropriate plugin
1916 if (OK == stat(pathname, &stat_buf)) {
1917 if (S_ISDIR(stat_buf.st_mode)) {
1918 is_dir = true;
1919 } else if (stat_buf.st_size == 0) {
1920 errno = EBADF;
1921 return job_handle;
1922 }
1923 } else {
1924 errno = ENOENT;
1925 return job_handle;
1926 }
1927
1928 // Make sure we have job_params
1929 if (job_params == NULL) {
1930 errno = ECOMM;
1931 return job_handle;
1932 }
1933
1934 plugin = plugin_search(mime_type, print_format);
1935 _lock();
1936
1937 if (plugin) {
1938 job_handle = _get_handle();
1939 if (job_handle == WPRINT_BAD_JOB_HANDLE) {
1940 errno = EAGAIN;
1941 }
1942 } else {
1943 errno = ENOSYS;
1944 LOGE("wprintStartJob(): ERROR: no plugin found for %s => %s", mime_type, print_format);
1945 }
1946
1947 if (job_handle != WPRINT_BAD_JOB_HANDLE) {
1948 print_ifc = (ifc_print_job_t *) _get_print_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP));
1949
1950 // fill out the job queue record
1951 jq = _get_job_desc(job_handle);
1952 if (jq == NULL) {
1953 _recycle_handle(job_handle);
1954 job_handle = WPRINT_BAD_JOB_HANDLE;
1955 _unlock();
1956 return job_handle;
1957 }
1958
1959 if (debugDir != NULL) {
1960 strncpy(jq->debug_path, debugDir, MAX_PATHNAME_LENGTH);
1961 jq->debug_path[MAX_PATHNAME_LENGTH] = 0;
1962 }
1963
1964 strncpy(jq->printer_addr, printer_addr, MAX_PRINTER_ADDR_LENGTH);
1965 strncpy(jq->mime_type, mime_type, MAX_MIME_LENGTH);
1966 strncpy(jq->pathname, pathname, MAX_PATHNAME_LENGTH);
1967
1968 jq->port_num = port_num;
1969 jq->cb_fn = cb_fn;
1970 jq->print_ifc = print_ifc;
1971 jq->cancel_ok = true; // assume cancel is ok
1972 jq->plugin = plugin;
1973 memcpy(jq->printer_uri, printer_cap->httpResource,
1974 MIN(ARRAY_SIZE(printer_cap->httpResource), ARRAY_SIZE(jq->printer_uri)));
1975
1976 jq->status_ifc = _get_status_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP));
1977
1978 memcpy((char *) &(jq->job_params), job_params, sizeof(wprint_job_params_t));
1979
1980 jq->use_secure_uri = (strstr(scheme, IPPS_PREFIX) != NULL);
1981
1982 size_t useragent_len = strlen(USERAGENT_PREFIX) + strlen(jq->job_params.docCategory) + 1;
1983 char *useragent = (char *) malloc(useragent_len);
1984 if (useragent != NULL) {
1985 snprintf(useragent, useragent_len, USERAGENT_PREFIX "%s", jq->job_params.docCategory);
1986 jq->job_params.useragent = useragent;
1987 }
1988
1989 // Make a copy of the job_params certificate if it is present
1990 if (job_params->certificate) {
1991 jq->job_params.certificate = malloc(job_params->certificate_len);
1992 if (jq->job_params.certificate) {
1993 memcpy(jq->job_params.certificate, job_params->certificate,
1994 job_params->certificate_len);
1995 }
1996 }
1997
1998 jq->job_params.page_num = 0;
1999 jq->job_params.print_format = print_format;
2000 if (strcmp(print_format, PRINT_FORMAT_PCLM) == 0) {
2001 if (printer_cap->canPrintPCLm || printer_cap->canPrintPDF) {
2002 jq->job_params.pcl_type = PCLm;
2003 } else {
2004 jq->job_params.pcl_type = PCLNONE;
2005 }
2006 }
2007
2008 if (strcmp(print_format, PRINT_FORMAT_PWG) == 0) {
2009 if (printer_cap->canPrintPWG) {
2010 jq->job_params.pcl_type = PCLPWG;
2011 } else {
2012 jq->job_params.pcl_type = PCLNONE;
2013 }
2014 }
2015
2016 // if the pathname is a directory, then this is a multi-page job with individual pages
2017 if (is_dir) {
2018 jq->is_dir = true;
2019 jq->num_pages = 0;
2020
2021 // create a pageQ for queuing page information
2022 jq->pageQ = msgQCreate(_MAX_PAGES_PER_JOB, sizeof(_page_t));
2023
2024 // create a secondary page Q for subsequently saving page data for copies #2 to n
2025 if (jq->job_params.num_copies > 1) {
2026 jq->saveQ = msgQCreate(_MAX_PAGES_PER_JOB, sizeof(_page_t));
2027 }
2028 } else {
2029 jq->num_pages = 1;
2030 }
2031
2032 // post a message with job_handle to the msgQ that is serviced by a thread
2033 msg.id = MSG_RUN_JOB;
2034 msg.job_id = job_handle;
2035
2036 if (print_ifc && plugin && plugin->print_page &&
2037 (msgQSend(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT, MSG_Q_FIFO) == OK)) {
2038 errno = OK;
2039 LOGD("wprintStartJob(): print job %ld queued (%s => %s)", job_handle,
2040 mime_type, print_format);
2041 } else {
2042 if (print_ifc == NULL) {
2043 errno = EAFNOSUPPORT;
2044 } else if ((plugin == NULL) || (plugin->print_page == NULL)) {
2045 errno = ELIBACC;
2046 } else {
2047 errno = EBADMSG;
2048 }
2049
2050 LOGE("wprintStartJob(): ERROR plugin->start_job(%ld) : %s => %s", job_handle,
2051 mime_type, print_format);
2052 jq->job_state = JOB_STATE_ERROR;
2053 _recycle_handle(job_handle);
2054 job_handle = WPRINT_BAD_JOB_HANDLE;
2055 }
2056 }
2057 _unlock();
2058 return job_handle;
2059 }
2060
wprintEndJob(wJob_t job_handle)2061 status_t wprintEndJob(wJob_t job_handle) {
2062 _page_t page;
2063 _job_queue_t *jq;
2064 status_t result = ERROR;
2065
2066 _lock();
2067 jq = _get_job_desc(job_handle);
2068
2069 if (jq) {
2070 // if the job is done and is to be freed, do it
2071 if ((jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_ERROR) ||
2072 (jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_COMPLETED)) {
2073 result = OK;
2074 if (jq->pageQ) {
2075 while ((msgQNumMsgs(jq->pageQ) > 0)
2076 && (msgQReceive(jq->pageQ, (char *) &page, sizeof(page),
2077 WAIT_FOREVER) == OK)) {
2078 }
2079 result |= msgQDelete(jq->pageQ);
2080 jq->pageQ = NULL;
2081 }
2082
2083 if (jq->saveQ) {
2084 while ((msgQNumMsgs(jq->saveQ) > 0)
2085 && (msgQReceive(jq->saveQ, (char *) &page, sizeof(page),
2086 WAIT_FOREVER) == OK)) {
2087 }
2088 result |= msgQDelete(jq->saveQ);
2089 jq->saveQ = NULL;
2090 }
2091 _recycle_handle(job_handle);
2092 } else {
2093 LOGE("job %ld cannot be ended from state %d", job_handle, jq->job_state);
2094 }
2095 } else {
2096 LOGE("ERROR: wprintEndJob(%ld), job not found", job_handle);
2097 }
2098
2099 _unlock();
2100 return result;
2101 }
2102
wprintPage(wJob_t job_handle,int page_num,const char * filename,bool last_page,bool pdf_page,unsigned int top_margin,unsigned int left_margin,unsigned int right_margin,unsigned int bottom_margin)2103 status_t wprintPage(wJob_t job_handle, int page_num, const char *filename, bool last_page,
2104 bool pdf_page, unsigned int top_margin, unsigned int left_margin, unsigned int right_margin,
2105 unsigned int bottom_margin) {
2106 _job_queue_t *jq;
2107 _page_t page;
2108 status_t result = ERROR;
2109 struct stat stat_buf;
2110
2111 _lock();
2112 jq = _get_job_desc(job_handle);
2113
2114 // use empty string to indicate EOJ for an empty job
2115 if (!filename) {
2116 filename = "";
2117 last_page = true;
2118 } else if (OK == stat(filename, &stat_buf)) {
2119 if (!S_ISREG(stat_buf.st_mode) || (stat_buf.st_size == 0)) {
2120 _unlock();
2121 return result;
2122 }
2123 } else {
2124 _unlock();
2125 return result;
2126 }
2127
2128 // must be setup as a multi-page job, page_num must be valid, and filename must fit
2129 if (jq && jq->is_dir && !(jq->last_page_seen) && (((strlen(filename) < MAX_PATHNAME_LENGTH)) ||
2130 (jq && (strcmp(filename, "") == 0) && last_page))) {
2131 memset(&page, 0, sizeof(page));
2132 page.page_num = page_num;
2133 page.corrupted = false;
2134 page.pdf_page = pdf_page;
2135 page.last_page = last_page;
2136 page.top_margin = top_margin;
2137 page.left_margin = left_margin;
2138 page.right_margin = right_margin;
2139 page.bottom_margin = bottom_margin;
2140
2141 if ((strlen(filename) == 0) || strchr(filename, '/')) {
2142 // assume empty or complete pathname and use it as it is
2143 strncpy(page.filename, filename, MAX_PATHNAME_LENGTH);
2144 } else {
2145 // generate a complete pathname
2146 snprintf(page.filename, MAX_PATHNAME_LENGTH, "%s/%s", jq->pathname, filename);
2147 }
2148
2149 if (last_page) {
2150 jq->last_page_seen = true;
2151 }
2152
2153 result = msgQSend(jq->pageQ, (char *) &page, sizeof(page), NO_WAIT, MSG_Q_FIFO);
2154 }
2155
2156 if (result == OK) {
2157 LOGD("wprintPage(%ld, %d, %s, %d)", job_handle, page_num, filename, last_page);
2158 if (!(last_page && (strcmp(filename, "") == 0))) {
2159 jq->num_pages++;
2160 }
2161 } else {
2162 LOGE("wprintPage(%ld, %d, %s, %d)", job_handle, page_num, filename, last_page);
2163 }
2164
2165 _unlock();
2166 return result;
2167 }
2168
wprintCancelJob(wJob_t job_handle)2169 status_t wprintCancelJob(wJob_t job_handle) {
2170 _job_queue_t *jq;
2171 status_t result;
2172
2173 _lock();
2174
2175 jq = _get_job_desc(job_handle);
2176
2177 if (jq) {
2178 LOGI("received cancel request");
2179 // send an empty page in case we're waiting on the msgQ page receive
2180 if ((jq->job_state == JOB_STATE_RUNNING) || (jq->job_state == JOB_STATE_BLOCKED)) {
2181 bool enableTimeout = true;
2182 jq->cancel_ok = true;
2183 jq->job_params.cancelled = true;
2184 wprintPage(job_handle, jq->num_pages + 1, NULL, true, false, 0, 0, 0, 0);
2185 if (jq->status_ifc) {
2186 // are we blocked waiting for the job to start
2187 if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->job_params.page_num != 0)) {
2188 errno = OK;
2189 jq->cancel_ok = ((jq->status_ifc->cancel)(jq->status_ifc,
2190 jq->job_params.job_originating_user_name) == 0);
2191 if ((jq->cancel_ok == true) && (errno != OK)) {
2192 enableTimeout = false;
2193 }
2194 }
2195 }
2196 if (!jq->cancel_ok) {
2197 LOGE("CANCEL did not go through or is not supported for this device");
2198 enableTimeout = true;
2199 }
2200 if (enableTimeout && (jq->print_ifc != NULL) &&
2201 (jq->print_ifc->enable_timeout != NULL)) {
2202 jq->print_ifc->enable_timeout(jq->print_ifc, 1);
2203 }
2204
2205 errno = (jq->cancel_ok ? OK : ENOTSUP);
2206 jq->job_state = JOB_STATE_CANCEL_REQUEST;
2207 result = OK;
2208 } else if ((jq->job_state == JOB_STATE_CANCEL_REQUEST) ||
2209 (jq->job_state == JOB_STATE_CANCELLED)) {
2210 result = OK;
2211 errno = (jq->cancel_ok ? OK : ENOTSUP);
2212 } else if (jq->job_state == JOB_STATE_QUEUED) {
2213 jq->job_params.cancelled = true;
2214 jq->job_state = JOB_STATE_CANCELLED;
2215
2216 if (jq->cb_fn) {
2217 wprint_job_callback_params_t cb_param;
2218 cb_param.param.state = JOB_DONE;
2219 cb_param.blocked_reasons = BLOCKED_REASONS_CANCELLED;
2220 cb_param.job_done_result = CANCELLED;
2221 cb_param.certificate = jq->certificate;
2222 cb_param.certificate_len = jq->certificate_len;
2223
2224 jq->cb_fn(job_handle, (void *) &cb_param);
2225 }
2226
2227 errno = OK;
2228 result = OK;
2229 } else {
2230 LOGE("job in other state");
2231 result = ERROR;
2232 errno = EBADRQC;
2233 }
2234 } else {
2235 LOGE("could not find job");
2236 result = ERROR;
2237 errno = EBADR;
2238 }
2239
2240 _unlock();
2241
2242 return result;
2243 }
2244
wprintExit(void)2245 status_t wprintExit(void) {
2246 _msg_t msg;
2247
2248 if (_msgQ) {
2249 // toss the remaining messages in the msgQ
2250 while ((msgQNumMsgs(_msgQ) > 0) &&
2251 (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT))) {}
2252
2253 // send a quit message
2254 msg.id = MSG_QUIT;
2255 msgQSend(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT, MSG_Q_FIFO);
2256
2257 // stop the job thread
2258 _stop_thread();
2259
2260 // empty out the semaphore
2261 while (sem_trywait(&_job_end_wait_sem) == OK);
2262 while (sem_trywait(&_job_start_wait_sem) == OK);
2263
2264 // receive any messages just in case
2265 while ((msgQNumMsgs(_msgQ) > 0)
2266 && (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT))) {}
2267
2268 // delete the msgQ
2269 msgQDelete(_msgQ);
2270 _msgQ = NULL;
2271
2272 sem_destroy(&_job_end_wait_sem);
2273 sem_destroy(&_job_start_wait_sem);
2274
2275 pthread_mutex_destroy(&_q_lock);
2276 stop_mutex = true;
2277 }
2278
2279 return OK;
2280 }
2281
wprintSetSourceInfo(const char * appName,const char * appVersion,const char * osName)2282 void wprintSetSourceInfo(const char *appName, const char *appVersion, const char *osName) {
2283 if (appName) {
2284 strncpy(g_appName, appName, (sizeof(g_appName) - 1));
2285 }
2286
2287 if (appVersion) {
2288 strncpy(g_appVersion, appVersion, (sizeof(g_appVersion) - 1));
2289 }
2290
2291 if (osName) {
2292 strncpy(g_osName, osName, (sizeof(g_osName) - 1));
2293 }
2294
2295 LOGI("App Name: '%s', Version: '%s', OS: '%s'", g_appName, g_appVersion, g_osName);
2296 }
2297
wprintBlankPageForPclm(const wprint_job_params_t * job_params,const printer_capabilities_t * printer_cap)2298 bool wprintBlankPageForPclm(const wprint_job_params_t *job_params,
2299 const printer_capabilities_t *printer_cap) {
2300 return ((job_params->job_pages_per_set % 2) &&
2301 ((job_params->num_copies > 1 && printer_cap->sidesSupported) ||
2302 (job_params->num_copies == 1)) && (job_params->duplex != DUPLEX_MODE_NONE));
2303 }
2304
wprintBlankPageForPwg(const wprint_job_params_t * job_params,const printer_capabilities_t * printer_cap)2305 bool wprintBlankPageForPwg(const wprint_job_params_t *job_params,
2306 const printer_capabilities_t *printer_cap) {
2307 return ((job_params->job_pages_per_set % 2) && (job_params->duplex != DUPLEX_MODE_NONE) &&
2308 !(printer_cap->jobPagesPerSetSupported &&
2309 strcmp(job_params->print_format, PRINT_FORMAT_PWG) == 0));
2310 }