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