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