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