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 }