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