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