Home | History | Annotate | Download | only in lib
      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  */
    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  */
    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 
    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 
    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 
    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 
    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 
    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 
    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  */
    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 
    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 
    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 
    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 
    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  */
    405 static void _lock(void) {
    406     pthread_mutex_lock(&_q_lock);
    407 }
    408 
    409 /*
    410  * Unlock the semaphore for this module
    411  */
    412 static void _unlock(void) {
    413     pthread_mutex_unlock(&_q_lock);
    414 }
    415 
    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 
    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  */
    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  */
    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 
    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 
    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  */
    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  */
   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  */
   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 
   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 
   1315 static void _setup_print_plugins() {
   1316     plugin_reset();
   1317     plugin_add(libwprintplugin_pcl_reg());
   1318     plugin_add(libwprintplugin_pdf_reg());
   1319 }
   1320 
   1321 bool wprintIsRunning() {
   1322     return _msgQ != 0;
   1323 }
   1324 
   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  */
   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  */
   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  */
   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  */
   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  */
   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 
   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  */
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 
   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 }