/* * Copyright (C) 2016 The Android Open Source Project * Copyright (C) 2016 Mopria Alliance, Inc. * Copyright (C) 2013 Hewlett-Packard Development Company, L.P. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef __USE_UNIX98 #define __USE_UNIX98 #endif #include #include #include #include "ifc_print_job.h" #include "wprint_debug.h" #include "plugin_db.h" #include "ifc_status_monitor.h" #include "ippstatus_monitor.h" #include "ippstatus_capabilities.h" #include "ipp_print.h" #include "ipphelper.h" #include "lib_printable_area.h" #include "wprint_io_plugin.h" #include "../plugins/media.h" #define TAG "lib_wprint" /* As expected by target devices */ #define USERAGENT_PREFIX "wPrintAndroid" #define USE_PWG_OVER_PCLM 0 #if (USE_PWG_OVER_PCLM != 0) #define _DEFAULT_PRINT_FORMAT PRINT_FORMAT_PWG #define _DEFAULT_PCL_TYPE PCLPWG #else // (USE_PWG_OVER_PCLM != 0) #define _DEFAULT_PRINT_FORMAT PRINT_FORMAT_PCLM #define _DEFAULT_PCL_TYPE PCLm #endif // (USE_PWG_OVER_PCLM != 0) #define _MAX_SPOOLED_JOBS 100 #define _MAX_MSGS (_MAX_SPOOLED_JOBS * 5) #define _MAX_PAGES_PER_JOB 1000 #define MAX_IDLE_WAIT (5 * 60) #define DEFAULT_RESOLUTION (300) // When searching for a supported resolution this is the max resolution we will consider. #define MAX_SUPPORTED_RESOLUTION (720) #define MAX_DONE_WAIT (5 * 60) #define MAX_START_WAIT (45) #define IO_PORT_FILE 0 /* * The following macros allow for up to 8 bits (256) for spooled job id#s and * 24 bits (16 million) of a running sequence number to provide a reasonably * unique job handle */ // _ENCODE_HANDLE() is only called from _get_handle() #define _ENCODE_HANDLE(X) ( (((++_running_number) & 0xffffff) << 8) | ((X) & 0xff) ) #define _DECODE_HANDLE(X) ((X) & 0xff) #undef snprintf #undef vsnprintf typedef enum { JOB_STATE_FREE, // queue element free JOB_STATE_QUEUED, // job queued and waiting to be run JOB_STATE_RUNNING, // job running (printing) JOB_STATE_BLOCKED, // print job blocked due to printer stall/error JOB_STATE_CANCEL_REQUEST, // print job cancelled by user, JOB_STATE_CANCELLED, // print job cancelled by user, waiting to be freed JOB_STATE_COMPLETED, // print job completed successfully, waiting to be freed JOB_STATE_ERROR, // job could not be run due to error JOB_STATE_CORRUPTED, // job could not be run due to error NUM_JOB_STATES } _job_state_t; typedef enum { TOP_MARGIN = 0, LEFT_MARGIN, RIGHT_MARGIN, BOTTOM_MARGIN, NUM_PAGE_MARGINS } _page_margins_t; typedef enum { MSG_RUN_JOB, MSG_QUIT, } wprint_msg_t; typedef struct { wprint_msg_t id; wJob_t job_id; } _msg_t; /* * Define an entry in the job queue */ typedef struct { wJob_t job_handle; _job_state_t job_state; unsigned int blocked_reasons; wprint_status_cb_t cb_fn; char *printer_addr; port_t port_num; wprint_plugin_t *plugin; ifc_print_job_t *print_ifc; char *mime_type; char *pathname; bool is_dir; bool last_page_seen; int num_pages; msg_q_id pageQ; msg_q_id saveQ; wprint_job_params_t job_params; bool cancel_ok; bool use_secure_uri; const ifc_status_monitor_t *status_ifc; char debug_path[MAX_PATHNAME_LENGTH + 1]; char printer_uri[1024]; int job_debug_fd; int page_debug_fd; /* A buffer of bytes containing the certificate received while setting up this job, if any. */ uint8 *certificate; int certificate_len; } _job_queue_t; /* * An entry for queued pages */ typedef struct { int page_num; bool pdf_page; bool last_page; bool corrupted; char filename[MAX_PATHNAME_LENGTH + 1]; unsigned int top_margin; unsigned int left_margin; unsigned int right_margin; unsigned int bottom_margin; } _page_t; /* * Entry for a registered plugin */ typedef struct { port_t port_num; const wprint_io_plugin_t *io_plugin; } _io_plugin_t; static _job_queue_t _job_queue[_MAX_SPOOLED_JOBS]; static msg_q_id _msgQ; static pthread_t _job_status_tid; static pthread_t _job_tid; static pthread_mutex_t _q_lock; static pthread_mutexattr_t _q_lock_attr; static sem_t _job_end_wait_sem; static sem_t _job_start_wait_sem; static _io_plugin_t _io_plugins[2]; char g_osName[MAX_ID_STRING_LENGTH + 1] = {0}; char g_appName[MAX_ID_STRING_LENGTH + 1] = {0}; char g_appVersion[MAX_ID_STRING_LENGTH + 1] = {0}; /* * Convert a pcl_t type to a human-readable string */ static char *getPCLTypeString(pcl_t pclenum) { switch (pclenum) { case PCLNONE: return "PCL_NONE"; case PCLm: return "PCLm"; case PCLJPEG: return "PCL_JPEG"; case PCLPWG: return "PWG-Raster"; default: return "unkonwn PCL Type"; } } /* * Return a _job_queue_t item by its job_handle or NULL if not found. */ static _job_queue_t *_get_job_desc(wJob_t job_handle) { unsigned long index; if (job_handle == WPRINT_BAD_JOB_HANDLE) { return NULL; } index = _DECODE_HANDLE(job_handle); if ((index < _MAX_SPOOLED_JOBS) && (_job_queue[index].job_handle == job_handle) && (_job_queue[index].job_state != JOB_STATE_FREE)) { return (&_job_queue[index]); } else { return NULL; } } /* * Functions below to fill out the _debug_stream_ifc interface */ static void _stream_dbg_end_job(wJob_t job_handle) { _job_queue_t *jq = _get_job_desc(job_handle); if (jq && (jq->job_debug_fd >= 0)) { close(jq->job_debug_fd); jq->job_debug_fd = -1; } } static void _stream_dbg_start_job(wJob_t job_handle, const char *ext) { _stream_dbg_end_job(job_handle); _job_queue_t *jq = _get_job_desc(job_handle); if (jq && jq->debug_path[0]) { char filename[MAX_PATHNAME_LENGTH + 1]; snprintf(filename, MAX_PATHNAME_LENGTH, "%s/jobstream.%s", jq->debug_path, ext); filename[MAX_PATHNAME_LENGTH] = 0; jq->job_debug_fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); } } static void _stream_dbg_job_data(wJob_t job_handle, const unsigned char *buff, unsigned long nbytes) { _job_queue_t *jq = _get_job_desc(job_handle); ssize_t bytes_written; if (jq && (jq->job_debug_fd >= 0)) { while (nbytes > 0) { bytes_written = write(jq->job_debug_fd, buff, nbytes); if (bytes_written < 0) { return; } nbytes -= bytes_written; buff += bytes_written; } } } static void _stream_dbg_end_page(wJob_t job_handle) { _job_queue_t *jq = _get_job_desc(job_handle); if (jq && (jq->page_debug_fd >= 0)) { close(jq->page_debug_fd); jq->page_debug_fd = -1; } } static void _stream_dbg_page_data(wJob_t job_handle, const unsigned char *buff, unsigned long nbytes) { _job_queue_t *jq = _get_job_desc(job_handle); ssize_t bytes_written; if (jq && (jq->page_debug_fd >= 0)) { while (nbytes > 0) { bytes_written = write(jq->page_debug_fd, buff, nbytes); if (bytes_written < 0) { return; } nbytes -= bytes_written; buff += bytes_written; } } } #define PPM_IDENTIFIER "P6\n" #define PPM_HEADER_LENGTH 128 static void _stream_dbg_start_page(wJob_t job_handle, int page_number, int width, int height) { _stream_dbg_end_page(job_handle); _job_queue_t *jq = _get_job_desc(job_handle); if (jq && jq->debug_path[0]) { union { char filename[MAX_PATHNAME_LENGTH + 1]; char ppm_header[PPM_HEADER_LENGTH + 1]; } buff; snprintf(buff.filename, MAX_PATHNAME_LENGTH, "%s/page%4.4d.ppm", jq->debug_path, page_number); buff.filename[MAX_PATHNAME_LENGTH] = 0; jq->page_debug_fd = open(buff.filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); int length = snprintf(buff.ppm_header, sizeof(buff.ppm_header), "%s\n#%*c\n%d %d\n%d\n", PPM_IDENTIFIER, 0, ' ', width, height, 255); int padding = sizeof(buff.ppm_header) - length; snprintf(buff.ppm_header, sizeof(buff.ppm_header), "%s\n#%*c\n%d %d\n%d\n", PPM_IDENTIFIER, padding, ' ', width, height, 255); _stream_dbg_page_data(job_handle, (const unsigned char *) buff.ppm_header, PPM_HEADER_LENGTH); } } static const ifc_wprint_debug_stream_t _debug_stream_ifc = { .debug_start_job = _stream_dbg_start_job, .debug_job_data = _stream_dbg_job_data, .debug_end_job = _stream_dbg_end_job, .debug_start_page = _stream_dbg_start_page, .debug_page_data = _stream_dbg_page_data, .debug_end_page = _stream_dbg_end_page }; /* * Return the debug stream interface corresponding to the specified job handle */ const ifc_wprint_debug_stream_t *getDebugStreamIfc(wJob_t handle) { _job_queue_t *jq = _get_job_desc(handle); if (jq) { return (jq->debug_path[0] == 0) ? NULL : &_debug_stream_ifc; } return NULL; } const ifc_wprint_t _wprint_ifc = { .msgQCreate = msgQCreate, .msgQDelete = msgQDelete, .msgQSend = msgQSend, .msgQReceive = msgQReceive, .msgQNumMsgs = msgQNumMsgs, .get_debug_stream_ifc = getDebugStreamIfc }; static pcl_t _default_pcl_type = _DEFAULT_PCL_TYPE; static const ifc_print_job_t *_printer_file_connect(const ifc_wprint_t *wprint_ifc) { return printer_connect(IO_PORT_FILE); } static const ifc_printer_capabilities_t *_get_caps_ifc(port_t port_num) { int i; for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) { if (_io_plugins[i].port_num == port_num) { if (_io_plugins[i].io_plugin == NULL) { return NULL; } if (_io_plugins[i].io_plugin->getCapsIFC == NULL) { return NULL; } else { return (_io_plugins[i].io_plugin->getCapsIFC(&_wprint_ifc)); } } } return NULL; } static const ifc_status_monitor_t *_get_status_ifc(port_t port_num) { int i; for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) { if (_io_plugins[i].port_num == port_num) { if (_io_plugins[i].io_plugin == NULL) { return NULL; } if (_io_plugins[i].io_plugin->getStatusIFC == NULL) { return NULL; } else { return (_io_plugins[i].io_plugin->getStatusIFC(&_wprint_ifc)); } } } return NULL; } static const ifc_print_job_t *_get_print_ifc(port_t port_num) { int i; for (i = 0; i < ARRAY_SIZE(_io_plugins); i++) { if (_io_plugins[i].port_num == port_num) { if (_io_plugins[i].io_plugin == NULL) { return NULL; } if (_io_plugins[i].io_plugin->getPrintIFC == NULL) { return NULL; } else { return (_io_plugins[i].io_plugin->getPrintIFC(&_wprint_ifc)); } } } return NULL; } /* * Lock the semaphore for this module */ static void _lock(void) { pthread_mutex_lock(&_q_lock); } /* * Unlock the semaphore for this module */ static void _unlock(void) { pthread_mutex_unlock(&_q_lock); } static wJob_t _get_handle(void) { static unsigned long _running_number = 0; wJob_t job_handle = WPRINT_BAD_JOB_HANDLE; int i, index, size; char *ptr; for (i = 0; i < _MAX_SPOOLED_JOBS; i++) { index = (i + _running_number) % _MAX_SPOOLED_JOBS; if (_job_queue[index].job_state == JOB_STATE_FREE) { size = MAX_MIME_LENGTH + MAX_PRINTER_ADDR_LENGTH + MAX_PATHNAME_LENGTH + 4; ptr = malloc(size); if (ptr) { memset(&_job_queue[index], 0, sizeof(_job_queue_t)); memset(ptr, 0, size); _job_queue[index].job_debug_fd = -1; _job_queue[index].page_debug_fd = -1; _job_queue[index].printer_addr = ptr; ptr += (MAX_PRINTER_ADDR_LENGTH + 1); _job_queue[index].mime_type = ptr; ptr += (MAX_MIME_LENGTH + 1); _job_queue[index].pathname = ptr; _job_queue[index].job_state = JOB_STATE_QUEUED; _job_queue[index].job_handle = _ENCODE_HANDLE(index); job_handle = _job_queue[index].job_handle; } break; } } return job_handle; } static int _recycle_handle(wJob_t job_handle) { _job_queue_t *jq = _get_job_desc(job_handle); if (jq == NULL) { return ERROR; } else if ((jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_ERROR) || (jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_COMPLETED)) { if (jq->print_ifc != NULL) { jq->print_ifc->destroy(jq->print_ifc); } jq->print_ifc = NULL; if (jq->status_ifc != NULL) { jq->status_ifc->destroy(jq->status_ifc); } jq->status_ifc = NULL; if (jq->job_params.useragent != NULL) { free((void *) jq->job_params.useragent); } if (jq->job_params.certificate != NULL) { free((void *) jq->job_params.certificate); } free(jq->printer_addr); jq->job_state = JOB_STATE_FREE; if (jq->job_debug_fd != -1) { close(jq->job_debug_fd); } jq->job_debug_fd = -1; if (jq->page_debug_fd != -1) { close(jq->page_debug_fd); } jq->page_debug_fd = -1; jq->debug_path[0] = 0; if (jq->certificate) { free(jq->certificate); jq->certificate = NULL; } return OK; } else { return ERROR; } } /* * Stops the job status thread if it exists */ static int _stop_status_thread(_job_queue_t *jq) { if (!pthread_equal(_job_status_tid, pthread_self()) && (jq && jq->status_ifc)) { (jq->status_ifc->stop)(jq->status_ifc); _unlock(); pthread_join(_job_status_tid, 0); _lock(); _job_status_tid = pthread_self(); return OK; } else { return ERROR; } } /* * Handles a new status message from the printer. Based on the status of wprint and the printer, * this function will start/end a job, send another page, or return blocking errors. */ static void _job_status_callback(const printer_state_dyn_t *new_status, const printer_state_dyn_t *old_status, void *param) { wprint_job_callback_params_t cb_param; _job_queue_t *jq = (_job_queue_t *) param; unsigned int i, blocked_reasons; print_status_t statusnew, statusold; statusnew = new_status->printer_status & ~PRINTER_IDLE_BIT; statusold = old_status->printer_status & ~PRINTER_IDLE_BIT; cb_param.certificate = jq->certificate; cb_param.certificate_len = jq->certificate_len; LOGD("_job_status_callback(): current printer state: %d", statusnew); blocked_reasons = 0; for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) { if (new_status->printer_reasons[i] == PRINT_STATUS_MAX_STATE) { break; } LOGD("_job_status_callback(): blocking reason %d: %d", i, new_status->printer_reasons[i]); blocked_reasons |= (1 << new_status->printer_reasons[i]); } switch (statusnew) { case PRINT_STATUS_UNKNOWN: if ((new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE) || (new_status->printer_reasons[0] == PRINT_STATUS_UNKNOWN)) { sem_post(&_job_start_wait_sem); sem_post(&_job_end_wait_sem); _lock(); if ((new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE) && ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL))) { jq->print_ifc->enable_timeout(jq->print_ifc, 1); } _unlock(); } break; case PRINT_STATUS_IDLE: if ((statusold > PRINT_STATUS_IDLE) || (statusold == PRINT_STATUS_CANCELLED)) { // Print is over but the job wasn't ended correctly if (jq->is_dir && !jq->last_page_seen) { wprintPage(jq->job_handle, jq->num_pages + 1, NULL, true, false, 0, 0, 0, 0); } sem_post(&_job_end_wait_sem); } break; case PRINT_STATUS_CANCELLED: sem_post(&_job_start_wait_sem); if ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL)) { jq->print_ifc->enable_timeout(jq->print_ifc, 1); } if (statusold != PRINT_STATUS_CANCELLED) { LOGI("status requested job cancel"); if (new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE) { sem_post(&_job_start_wait_sem); sem_post(&_job_end_wait_sem); if ((jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL)) { jq->print_ifc->enable_timeout(jq->print_ifc, 1); } } _lock(); jq->job_params.cancelled = true; _unlock(); } if (new_status->printer_reasons[0] == PRINT_STATUS_OFFLINE) { sem_post(&_job_start_wait_sem); sem_post(&_job_end_wait_sem); } break; case PRINT_STATUS_PRINTING: sem_post(&_job_start_wait_sem); _lock(); if ((jq->job_state != JOB_STATE_RUNNING) || (jq->blocked_reasons != blocked_reasons)) { jq->job_state = JOB_STATE_RUNNING; jq->blocked_reasons = blocked_reasons; if (jq->cb_fn) { cb_param.state = JOB_RUNNING; cb_param.blocked_reasons = jq->blocked_reasons; cb_param.job_done_result = OK; jq->cb_fn(jq->job_handle, (void *) &cb_param); } } _unlock(); break; case PRINT_STATUS_UNABLE_TO_CONNECT: sem_post(&_job_start_wait_sem); _lock(); _stop_status_thread(jq); jq->blocked_reasons = blocked_reasons; jq->job_params.cancelled = true; jq->job_state = JOB_STATE_ERROR; if (jq->cb_fn) { cb_param.state = JOB_DONE; cb_param.blocked_reasons = blocked_reasons; cb_param.job_done_result = ERROR; jq->cb_fn(jq->job_handle, (void *) &cb_param); } if (jq->print_ifc != NULL) { jq->print_ifc->destroy(jq->print_ifc); jq->print_ifc = NULL; } if (jq->status_ifc != NULL) { jq->status_ifc->destroy(jq->status_ifc); jq->status_ifc = NULL; } _unlock(); sem_post(&_job_end_wait_sem); break; default: // an error has occurred, report it back to the client sem_post(&_job_start_wait_sem); _lock(); if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->blocked_reasons != blocked_reasons)) { jq->job_state = JOB_STATE_BLOCKED; jq->blocked_reasons = blocked_reasons; if (jq->cb_fn) { cb_param.state = JOB_BLOCKED; cb_param.blocked_reasons = blocked_reasons; cb_param.job_done_result = OK; jq->cb_fn(jq->job_handle, (void *) &cb_param); } } _unlock(); break; } } static void *_job_status_thread(void *param) { _job_queue_t *jq = (_job_queue_t *) param; (jq->status_ifc->start)(jq->status_ifc, _job_status_callback, param); return NULL; } static int _start_status_thread(_job_queue_t *jq) { sigset_t allsig, oldsig; int result = ERROR; if ((jq == NULL) || (jq->status_ifc == NULL)) { return result; } result = OK; sigfillset(&allsig); #if CHECK_PTHREAD_SIGMASK_STATUS result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig); #else // else CHECK_PTHREAD_SIGMASK_STATUS pthread_sigmask(SIG_SETMASK, &allsig, &oldsig); #endif // CHECK_PTHREAD_SIGMASK_STATUS if (result == OK) { result = pthread_create(&_job_status_tid, 0, _job_status_thread, jq); if ((result == ERROR) && (_job_status_tid != pthread_self())) { #if USE_PTHREAD_CANCEL pthread_cancel(_job_status_tid); #else // else USE_PTHREAD_CANCEL pthread_kill(_job_status_tid, SIGKILL); #endif // USE_PTHREAD_CANCEL _job_status_tid = pthread_self(); } } if (result == OK) { sched_yield(); #if CHECK_PTHREAD_SIGMASK_STATUS result = pthread_sigmask(SIG_SETMASK, &oldsig, 0); #else // else CHECK_PTHREAD_SIGMASK_STATUS pthread_sigmask(SIG_SETMASK, &oldsig, 0); #endif // CHECK_PTHREAD_SIGMASK_STATUS } return result; } /* * Return true unless the server gave an unexpected certificate */ static bool _is_certificate_allowed(_job_queue_t *jq) { int result = true; // Compare certificates if both are known if (jq->job_params.certificate && jq->certificate) { if (jq->job_params.certificate_len != jq->certificate_len) { LOGD("_is_certificate_allowed: certificate length mismatch allowed=%d, received=%d", jq->job_params.certificate_len, jq->certificate_len); result = false; } else if (0 != memcmp(jq->job_params.certificate, jq->certificate, jq->certificate_len)) { LOGD("_is_certificate_allowed: certificate content mismatch"); result = false; } else { LOGD("_is_certificate_allowed: certificate match, len=%d", jq->job_params.certificate_len); } } return result; } /* * Callback from lower layers containing certificate data, if any. */ static int _validate_certificate(wprint_connect_info_t *connect_info, uint8 *data, int data_len) { _job_queue_t *jq = connect_info->user; LOGD("_validate_certificate: %s://%s:%d%s handling server cert len=%d for job %ld", connect_info->uri_scheme, connect_info->printer_addr, connect_info->port_num, connect_info->uri_path, data_len, jq->job_handle); // Free any old certificate we have and save new certificate data if (jq->certificate) { free(jq->certificate); jq->certificate = NULL; } jq->certificate = (uint8 *)malloc(data_len); int error = 0; if (jq->certificate == NULL) { LOGD("_validate_certificate: malloc failed"); error = -1; } else { memcpy(jq->certificate, data, data_len); jq->certificate_len = data_len; if (!_is_certificate_allowed(jq)) { LOGD("_validate_certificate: received certificate disallowed."); error = -1; } } return error; } /* * Initialize the status interface (so we can use it to query for printer status. */ static void _initialize_status_ifc(_job_queue_t *jq) { wprint_connect_info_t connect_info; connect_info.printer_addr = jq->printer_addr; connect_info.uri_path = jq->printer_uri; connect_info.port_num = jq->port_num; if (jq->use_secure_uri) { connect_info.uri_scheme = IPPS_PREFIX; connect_info.user = jq; connect_info.validate_certificate = _validate_certificate; } else { connect_info.uri_scheme = IPP_PREFIX; connect_info.validate_certificate = NULL; } connect_info.timeout = DEFAULT_IPP_TIMEOUT; // Initialize the status interface with this connection info jq->status_ifc->init(jq->status_ifc, &connect_info); } /* * Runs a print job. Contains logic for what to do given different printer statuses. */ static void *_job_thread(void *param) { wprint_job_callback_params_t cb_param = { 0 }; _msg_t msg; wJob_t job_handle; _job_queue_t *jq; _page_t page; int i; status_t job_result; int corrupted = 0; while (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), WAIT_FOREVER)) { if (msg.id == MSG_RUN_JOB) { LOGI("_job_thread(): Received message: MSG_RUN_JOB"); } else { LOGI("_job_thread(): Received message: MSG_QUIT"); } if (msg.id == MSG_QUIT) { break; } job_handle = msg.job_id; // check if this is a valid job_handle that is still active _lock(); jq = _get_job_desc(job_handle); // set state to running and invoke the plugin, there is one if (jq) { if (jq->job_state != JOB_STATE_QUEUED) { _unlock(); continue; } corrupted = 0; job_result = OK; jq->job_params.plugin_data = NULL; // clear out the semaphore just in case while (sem_trywait(&_job_start_wait_sem) == OK) { } while (sem_trywait(&_job_end_wait_sem) == OK) { } // initialize the status ifc if (jq->status_ifc != NULL) { _initialize_status_ifc(jq); } // wait for the printer to be idle if ((jq->status_ifc != NULL) && (jq->status_ifc->get_status != NULL)) { int retry = 0; bool idle = false; printer_state_dyn_t printer_state; while (!idle) { print_status_t status; jq->status_ifc->get_status(jq->status_ifc, &printer_state); status = printer_state.printer_status & ~PRINTER_IDLE_BIT; // Pass along any certificate received in future callbacks cb_param.certificate = jq->certificate; cb_param.certificate_len = jq->certificate_len; // Presume we found an idle state idle = true; if (status == PRINT_STATUS_IDLE) { printer_state.printer_status = PRINT_STATUS_IDLE; jq->blocked_reasons = 0; } else if (status == PRINT_STATUS_UNKNOWN && printer_state.printer_reasons[0] == PRINT_STATUS_UNKNOWN) { // no status available, break out and hope for the best printer_state.printer_status = PRINT_STATUS_IDLE; } else if ((status == PRINT_STATUS_UNKNOWN || status == PRINT_STATUS_SVC_REQUEST) && ((printer_state.printer_reasons[0] == PRINT_STATUS_UNABLE_TO_CONNECT) || (printer_state.printer_reasons[0] == PRINT_STATUS_OFFLINE))) { if (_is_certificate_allowed(jq)) { LOGD("%s: Received an Unable to Connect message", __func__); jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT; } else { LOGD("%s: Bad certificate", __func__); jq->blocked_reasons = BLOCKED_REASON_BAD_CERTIFICATE; } } else if (printer_state.printer_status & PRINTER_IDLE_BIT) { LOGD("%s: printer blocked but appears to be in an idle state. " "Allowing job to proceed", __func__); printer_state.printer_status = PRINT_STATUS_IDLE; } else if (retry >= MAX_IDLE_WAIT) { jq->blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY; } else if (!jq->job_params.cancelled) { // Printer still appears busy, so stay in loop, notify, and poll again. idle = false; int blocked_reasons = 0; for (i = 0; i <= PRINT_STATUS_MAX_STATE; i++) { if (printer_state.printer_reasons[i] == PRINT_STATUS_MAX_STATE) { break; } blocked_reasons |= (1 << printer_state.printer_reasons[i]); } if (blocked_reasons == 0) { blocked_reasons |= BLOCKED_REASONS_PRINTER_BUSY; } if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->blocked_reasons != blocked_reasons)) { jq->job_state = JOB_STATE_BLOCKED; jq->blocked_reasons = blocked_reasons; if (jq->cb_fn) { cb_param.state = JOB_BLOCKED; cb_param.blocked_reasons = blocked_reasons; cb_param.job_done_result = OK; jq->cb_fn(jq->job_handle, (void *) &cb_param); } } _unlock(); sleep(1); _lock(); retry++; } } if (jq->job_params.cancelled) { job_result = CANCELLED; } else { job_result = (((printer_state.printer_status & ~PRINTER_IDLE_BIT) == PRINT_STATUS_IDLE) ? OK : ERROR); } } _job_status_tid = pthread_self(); if (job_result == OK) { if (jq->print_ifc) { job_result = jq->print_ifc->init(jq->print_ifc, jq->printer_addr, jq->port_num, jq->printer_uri, jq->use_secure_uri); if (job_result == ERROR) { jq->blocked_reasons = BLOCKED_REASON_UNABLE_TO_CONNECT; } } } if (job_result == OK) { _start_status_thread(jq); } /* call the plugin's start_job method, if no other job is running use callback to notify the client */ if ((job_result == OK) && jq->cb_fn) { cb_param.state = JOB_RUNNING; cb_param.blocked_reasons = 0; cb_param.job_done_result = OK; jq->cb_fn(job_handle, (void *) &cb_param); } jq->job_params.page_num = -1; if (job_result == OK) { if (jq->print_ifc != NULL) { LOGD("_job_thread: Calling validate_job"); if (jq->print_ifc->validate_job != NULL) { job_result = jq->print_ifc->validate_job(jq->print_ifc, &jq->job_params); } /* PDF format plugin's start_job and end_job are to be called for each copy, * inside the for-loop for num_copies. */ // Do not call start_job unless validate_job returned OK if ((job_result == OK) && (jq->print_ifc->start_job != NULL) && (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0)) { jq->print_ifc->start_job(jq->print_ifc, &jq->job_params); } } // Do not call start_job unless validate_job returned OK if (job_result == OK && jq->plugin->start_job != NULL) { job_result = jq->plugin->start_job(job_handle, (void *) &_wprint_ifc, (void *) jq->print_ifc, &(jq->job_params)); } } if (job_result == OK) { jq->job_params.page_num = 0; } // multi-page print job if (jq->is_dir && (job_result == OK)) { int per_copy_page_num; for (i = 0; (i < jq->job_params.num_copies) && ((job_result == OK) || (job_result == CORRUPT)) && (!jq->job_params.cancelled); i++) { if ((i > 0) && jq->job_params.copies_supported && (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) { LOGD("_job_thread multi_page: breaking out copies supported"); break; } bool pdf_printed = false; if (jq->print_ifc->start_job != NULL && (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) { jq->print_ifc->start_job(jq->print_ifc, &jq->job_params); } per_copy_page_num = 0; jq->job_state = JOB_STATE_RUNNING; // while there is a page to print _unlock(); while (OK == msgQReceive(jq->pageQ, (char *) &page, sizeof(page), WAIT_FOREVER)) { _lock(); // check for any printing problems so far if (jq->print_ifc->check_status) { if (jq->print_ifc->check_status(jq->print_ifc) == ERROR) { job_result = ERROR; break; } } /* take empty filename as cue to break out of the loop * but we have to do last_page processing */ // all copies are clubbed together as a single print job if (page.last_page && ((i == jq->job_params.num_copies - 1) || (jq->job_params.copies_supported && strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0))) { jq->job_params.last_page = page.last_page; } else { jq->job_params.last_page = false; } if (strlen(page.filename) > 0) { per_copy_page_num++; { jq->job_params.page_num++; } if (page.pdf_page) { jq->job_params.page_num = page.page_num; } else { jq->job_params.page_num = per_copy_page_num; } // setup page margin information jq->job_params.print_top_margin += page.top_margin; jq->job_params.print_left_margin += page.left_margin; jq->job_params.print_right_margin += page.right_margin; jq->job_params.print_bottom_margin += page.bottom_margin; jq->job_params.copy_num = (i + 1); jq->job_params.copy_page_num = page.page_num; jq->job_params.page_backside = !(per_copy_page_num & 0x1); jq->job_params.page_corrupted = (page.corrupted ? 1 : 0); jq->job_params.page_printing = true; _unlock(); if (!page.corrupted) { LOGD("_job_thread(): page not corrupt, calling plugin's print_page" " function for page #%d", page.page_num); if (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0) { job_result = jq->plugin->print_page(&(jq->job_params), jq->mime_type, page.filename); } else if (!pdf_printed) { // for PDF plugin, print_page prints entire document, // so need to be called only once job_result = jq->plugin->print_page(&(jq->job_params), jq->mime_type, page.filename); pdf_printed = true; } } else { LOGD("_job_thread(): page IS corrupt, printing blank page for " "page #%d", page.page_num); job_result = CORRUPT; if ((jq->job_params.duplex != DUPLEX_MODE_NONE) && (jq->plugin->print_blank_page != NULL)) { jq->plugin->print_blank_page(job_handle, &(jq->job_params)); } } _lock(); jq->job_params.print_top_margin -= page.top_margin; jq->job_params.print_left_margin -= page.left_margin; jq->job_params.print_right_margin -= page.right_margin; jq->job_params.print_bottom_margin -= page.bottom_margin; jq->job_params.page_printing = false; // make sure we only count corrupted pages once if (page.corrupted == false) { page.corrupted = ((job_result == CORRUPT) ? true : false); corrupted += (job_result == CORRUPT); } } // make sure we always print an even number of pages in duplex jobs if (page.last_page && (jq->job_params.duplex != DUPLEX_MODE_NONE) && !(jq->job_params.page_backside) && (jq->plugin->print_blank_page != NULL)) { _unlock(); jq->plugin->print_blank_page(job_handle, &(jq->job_params)); _lock(); } // if multiple copies are requested, save the contents of the pageQ message if (jq->saveQ && !jq->job_params.cancelled && (job_result != ERROR)) { job_result = msgQSend(jq->saveQ, (char *) &page, sizeof(page), NO_WAIT, MSG_Q_FIFO); // swap pageQ and saveQ if (page.last_page && !jq->job_params.last_page) { msg_q_id tmpQ = jq->pageQ; jq->pageQ = jq->saveQ; jq->saveQ = tmpQ; // defensive programming while (msgQNumMsgs(tmpQ) > 0) { msgQReceive(tmpQ, (char *) &page, sizeof(page), NO_WAIT); LOGE("pageQ inconsistencies, discarding page #%d, file %s", page.page_num, page.filename); } } } if (page.last_page || jq->job_params.cancelled) { // Leave the sempahore locked break; } // unlock to go back to the top of the while loop _unlock(); } // while there is another page if ((strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0) && (jq->print_ifc->end_job)) { int end_job_result = jq->print_ifc->end_job(jq->print_ifc); if (job_result == OK) { if (end_job_result == ERROR) { job_result = ERROR; } else if (end_job_result == CANCELLED) { job_result = CANCELLED; } } } } // for each copy of the job } else if (job_result == OK) { // single page job for (i = 0; ((i < jq->job_params.num_copies) && (job_result == OK)); i++) { if ((i > 0) && jq->job_params.copies_supported && (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) == 0)) { LOGD("_job_thread single_page: breaking out copies supported"); break; } // check for any printing problems so far if ((jq->print_ifc != NULL) && (jq->print_ifc->check_status)) { if (jq->print_ifc->check_status(jq->print_ifc) == ERROR) { job_result = ERROR; break; } } jq->job_state = JOB_STATE_RUNNING; jq->job_params.page_num++; jq->job_params.last_page = (i == (jq->job_params.num_copies - 1)); jq->job_params.copy_num = (i + 1); jq->job_params.copy_page_num = 1; jq->job_params.page_corrupted = (job_result == CORRUPT); jq->job_params.page_printing = true; _unlock(); job_result = jq->plugin->print_page(&(jq->job_params), jq->mime_type, jq->pathname); if ((jq->job_params.duplex != DUPLEX_MODE_NONE) && (jq->plugin->print_blank_page != NULL)) { jq->plugin->print_blank_page(job_handle, &(jq->job_params)); } _lock(); jq->job_params.page_printing = false; corrupted += (job_result == CORRUPT); } // for each copy } // if we started the job end it if (jq->job_params.page_num >= 0) { // if the job was cancelled without sending anything through, print a blank sheet if ((jq->job_params.page_num == 0) && (jq->plugin->print_blank_page != NULL)) { jq->plugin->print_blank_page(job_handle, &(jq->job_params)); } if (jq->plugin->end_job != NULL) { jq->plugin->end_job(&(jq->job_params)); } if ((jq->print_ifc != NULL) && (jq->print_ifc->end_job) && (strcmp(jq->job_params.print_format, PRINT_FORMAT_PDF) != 0)) { int end_job_result = jq->print_ifc->end_job(jq->print_ifc); if (job_result == OK) { if (end_job_result == ERROR) { job_result = ERROR; } else if (end_job_result == CANCELLED) { job_result = CANCELLED; } } } } // if we started to print, wait for idle if ((jq->job_params.page_num > 0) && (jq->status_ifc != NULL)) { int retry, result; _unlock(); for (retry = 0, result = ERROR; ((result == ERROR) && (retry <= MAX_START_WAIT)); retry++) { if (retry != 0) { sleep(1); } result = sem_trywait(&_job_start_wait_sem); } if (result == OK) { for (retry = 0, result = ERROR; ((result == ERROR) && (retry <= MAX_DONE_WAIT)); retry++) { if (retry != 0) { _lock(); if (jq->job_params.cancelled && !jq->cancel_ok) { /* The user tried to cancel and it either didn't go through * or the printer doesn't support cancel through an OID. * Either way it's pointless to sit here waiting for idle when * may never come, so we'll bail out early */ retry = (MAX_DONE_WAIT + 1); } _unlock(); sleep(1); if (retry == MAX_DONE_WAIT) { _lock(); if (!jq->job_params.cancelled && (jq->blocked_reasons & (BLOCKED_REASON_OUT_OF_PAPER | BLOCKED_REASON_JAMMED | BLOCKED_REASON_DOOR_OPEN))) { retry = (MAX_DONE_WAIT - 1); } _unlock(); } } result = sem_trywait(&_job_end_wait_sem); } } else { LOGD("_job_thread(): the job never started"); } _lock(); } // make sure page_num doesn't stay as a negative number jq->job_params.page_num = MAX(0, jq->job_params.page_num); _stop_status_thread(jq); if (corrupted != 0) { job_result = CORRUPT; } LOGI("job_thread(): with job_state value: %d ", jq->job_state); if ((jq->job_state == JOB_STATE_COMPLETED) || (jq->job_state == JOB_STATE_ERROR) || (jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_FREE)) { LOGI("_job_thread(): job finished early: do not send callback again"); } else { switch (job_result) { case OK: if (!jq->job_params.cancelled) { jq->job_state = JOB_STATE_COMPLETED; jq->blocked_reasons = 0; break; } else { job_result = CANCELLED; } case CANCELLED: jq->job_state = JOB_STATE_CANCELLED; jq->blocked_reasons = BLOCKED_REASONS_CANCELLED; if (!jq->cancel_ok) { jq->blocked_reasons |= BLOCKED_REASON_PARTIAL_CANCEL; } break; case CORRUPT: LOGE("_job_thread(): %d file(s) in the job were corrupted", corrupted); jq->job_state = JOB_STATE_CORRUPTED; jq->blocked_reasons = 0; break; case ERROR: default: LOGE("_job_thread(): ERROR plugin->start_job(%ld): %s => %s", job_handle, jq->mime_type, jq->job_params.print_format); job_result = ERROR; jq->job_state = JOB_STATE_ERROR; break; } // job_result // end of job callback if (jq->cb_fn) { cb_param.state = JOB_DONE; cb_param.blocked_reasons = jq->blocked_reasons; cb_param.job_done_result = job_result; jq->cb_fn(job_handle, (void *) &cb_param); } if (jq->print_ifc != NULL) { jq->print_ifc->destroy(jq->print_ifc); jq->print_ifc = NULL; } if (jq->status_ifc != NULL) { jq->status_ifc->destroy(jq->status_ifc); jq->status_ifc = NULL; } } } else { LOGI("_job_thread(): job %ld not in queue .. maybe cancelled", job_handle); } _unlock(); LOGI("_job_thread(): job finished: %ld", job_handle); } sem_post(&_job_end_wait_sem); return NULL; } /* * Starts the wprint background job thread */ static int _start_thread(void) { sigset_t allsig, oldsig; int result; _job_tid = pthread_self(); result = OK; sigfillset(&allsig); #if CHECK_PTHREAD_SIGMASK_STATUS result = pthread_sigmask(SIG_SETMASK, &allsig, &oldsig); #else // else CHECK_PTHREAD_SIGMASK_STATUS pthread_sigmask(SIG_SETMASK, &allsig, &oldsig); #endif // CHECK_PTHREAD_SIGMASK_STATUS if (result == OK) { result = pthread_create(&_job_tid, 0, _job_thread, NULL); if ((result == ERROR) && (_job_tid != pthread_self())) { #if USE_PTHREAD_CANCEL pthread_cancel(_job_tid); #else // else USE_PTHREAD_CANCEL pthread_kill(_job_tid, SIGKILL); #endif // USE_PTHREAD_CANCEL _job_tid = pthread_self(); } } if (result == OK) { sched_yield(); #if CHECK_PTHREAD_SIGMASK_STATUS result = pthread_sigmask(SIG_SETMASK, &oldsig, 0); #else // else CHECK_PTHREAD_SIGMASK_STATUS pthread_sigmask(SIG_SETMASK, &oldsig, 0); #endif // CHECK_PTHREAD_SIGMASK_STATUS } return result; } /* * Waits for the job thread to reach a stopped state */ static int _stop_thread(void) { if (!pthread_equal(_job_tid, pthread_self())) { pthread_join(_job_tid, 0); _job_tid = pthread_self(); return OK; } else { return ERROR; } } static const wprint_io_plugin_t _file_io_plugin = { .version = WPRINT_PLUGIN_VERSION(_INTERFACE_MINOR_VERSION), .port_num = PORT_FILE, .getCapsIFC = NULL, .getStatusIFC = NULL, .getPrintIFC = _printer_file_connect,}; static const wprint_io_plugin_t _ipp_io_plugin = { .version = WPRINT_PLUGIN_VERSION(_INTERFACE_MINOR_VERSION), .port_num = PORT_IPP, .getCapsIFC = ipp_status_get_capabilities_ifc, .getStatusIFC = ipp_status_get_monitor_ifc, .getPrintIFC = ipp_get_print_ifc,}; static void _setup_io_plugins() { _io_plugins[0].port_num = PORT_FILE; _io_plugins[0].io_plugin = &_file_io_plugin; _io_plugins[1].port_num = PORT_IPP; _io_plugins[1].io_plugin = &_ipp_io_plugin; } extern wprint_plugin_t *libwprintplugin_pcl_reg(void); extern wprint_plugin_t *libwprintplugin_pdf_reg(void); static void _setup_print_plugins() { plugin_reset(); plugin_add(libwprintplugin_pcl_reg()); plugin_add(libwprintplugin_pdf_reg()); } bool wprintIsRunning() { return _msgQ != 0; } int wprintInit(void) { int count = 0; _setup_print_plugins(); _setup_io_plugins(); _msgQ = msgQCreate(_MAX_MSGS, sizeof(_msg_t)); if (!_msgQ) { LOGE("ERROR: cannot create msgQ"); return ERROR; } sem_init(&_job_end_wait_sem, 0, 0); sem_init(&_job_start_wait_sem, 0, 0); signal(SIGPIPE, SIG_IGN); // avoid broken pipe process shutdowns pthread_mutexattr_settype(&_q_lock_attr, PTHREAD_MUTEX_RECURSIVE_NP); pthread_mutex_init(&_q_lock, &_q_lock_attr); if (_start_thread() != OK) { LOGE("could not start job thread"); return ERROR; } return count; } static const printer_capabilities_t _default_cap = {.color = true, .borderless = true, .numSupportedMediaSizes = 0, .numSupportedMediaTrays = 0, .numSupportedMediaTypes = 0,}; /* * Check if a media size is supported */ static bool is_supported(media_size_t media_size) { int i; for (i = 0; i < SUPPORTED_MEDIA_SIZE_COUNT; i++) { if (SupportedMediaSizes[i].media_size == media_size) return true; } return false; } /* * Return true if the specified int array of the supplied length contains a value. */ static bool int_array_contains(const int *array, int length, int value) { for (int i = 0; i < length; i++) { if (array[i] == value) return true; } return false; } /* * Checks printers reported media sizes and validates that wprint supports them */ static void _validate_supported_media_sizes(printer_capabilities_t *printer_cap) { if (printer_cap == NULL) return; if (printer_cap->numSupportedMediaSizes == 0) { unsigned int i = 0; printer_cap->supportedMediaSizes[i++] = ISO_A4; printer_cap->supportedMediaSizes[i++] = US_LETTER; printer_cap->supportedMediaSizes[i++] = INDEX_CARD_4X6; printer_cap->supportedMediaSizes[i++] = INDEX_CARD_5X7; printer_cap->numSupportedMediaSizes = i; } else { unsigned int read, write; for (read = write = 0; read < printer_cap->numSupportedMediaSizes; read++) { if (is_supported(printer_cap->supportedMediaSizes[read])) { printer_cap->supportedMediaSizes[write++] = printer_cap->supportedMediaSizes[read]; } } printer_cap->numSupportedMediaSizes = write; } } /* * Checks printers numSupportedMediaTrays. If none, then add Auto. */ static void _validate_supported_media_trays(printer_capabilities_t *printer_cap) { if (printer_cap == NULL) return; if (printer_cap->numSupportedMediaTrays == 0) { printer_cap->supportedMediaTrays[0] = TRAY_SRC_AUTO_SELECT; printer_cap->numSupportedMediaTrays = 1; } } /* * Add a printer's supported input formats to the capabilities struct */ static void _collect_supported_input_formats(printer_capabilities_t *printer_caps) { unsigned long long input_formats = 0; plugin_get_passthru_input_formats(&input_formats); // remove things the printer can't support if (!printer_caps->canPrintPDF) { input_formats &= ~(1 << INPUT_MIME_TYPE_PDF); } if (!printer_caps->canPrintPCLm) { input_formats &= ~(1 << INPUT_MIME_TYPE_PCLM); } if (!printer_caps->canPrintPWG) { input_formats &= ~(1 << INPUT_MIME_TYPE_PWG); } printer_caps->supportedInputMimeTypes = input_formats; } /* * Check the print resolutions supported by the printer and verify that wprint supports them. * If nothing is found, the desired resolution is selected. */ static unsigned int _findCloseResolutionSupported(int desiredResolution, int maxResolution, const printer_capabilities_t *printer_cap) { int closeResolution = 0; int closeDifference = 0; unsigned int index = 0; for (index = 0; index < printer_cap->numSupportedResolutions; index++) { int resolution = printer_cap->supportedResolutions[index]; if (resolution == desiredResolution) { // An exact match wins.. stop looking. return resolution; } else { int difference = abs(desiredResolution - resolution); if ((closeResolution == 0) || (difference < closeDifference)) { if (resolution <= maxResolution) { // We found a better match now.. record it but keep looking. closeResolution = resolution; closeDifference = difference; } } } } // If we get here we did not find an exact match. if (closeResolution == 0) { // We did not find anything.. just pick the desired value. closeResolution = desiredResolution; } return closeResolution; } status_t wprintGetCapabilities(const wprint_connect_info_t *connect_info, printer_capabilities_t *printer_cap) { LOGD("wprintGetCapabilities: Enter"); status_t result = ERROR; int index; int port_num = connect_info->port_num; const ifc_printer_capabilities_t *caps_ifc = NULL; memcpy(printer_cap, &_default_cap, sizeof(printer_capabilities_t)); caps_ifc = _get_caps_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP)); LOGD("wprintGetCapabilities: after getting caps ifc: %p", caps_ifc); switch (port_num) { case PORT_FILE: printer_cap->duplex = 1; printer_cap->borderless = 1; printer_cap->canPrintPCLm = (_default_pcl_type == PCLm); printer_cap->canPrintPWG = (_default_pcl_type == PCLPWG); printer_cap->stripHeight = STRIPE_HEIGHT; result = OK; break; default: break; } if (caps_ifc != NULL) { caps_ifc->init(caps_ifc, connect_info); result = caps_ifc->get_capabilities(caps_ifc, printer_cap); caps_ifc->destroy(caps_ifc); } _validate_supported_media_sizes(printer_cap); _collect_supported_input_formats(printer_cap); _validate_supported_media_trays(printer_cap); printer_cap->isSupported = (printer_cap->canPrintPCLm || printer_cap->canPrintPDF || printer_cap->canPrintPWG); if (result == OK) { LOGD("\tmake: %s", printer_cap->make); LOGD("\thas color: %d", printer_cap->color); LOGD("\tcan duplex: %d", printer_cap->duplex); LOGD("\tcan rotate back page: %d", printer_cap->canRotateDuplexBackPage); LOGD("\tcan print borderless: %d", printer_cap->borderless); LOGD("\tcan print pdf: %d", printer_cap->canPrintPDF); LOGD("\tcan print pclm: %d", printer_cap->canPrintPCLm); LOGD("\tcan print pwg: %d", printer_cap->canPrintPWG); LOGD("\tsource application name supported: %d", printer_cap->docSourceAppName); LOGD("\tsource application version supported: %d", printer_cap->docSourceAppVersion); LOGD("\tsource os name supported: %d", printer_cap->docSourceOsName); LOGD("\tsource os version supported: %d", printer_cap->docSourceOsVersion); LOGD("\tprinter supported: %d", printer_cap->isSupported); LOGD("\tstrip height: %d", printer_cap->stripHeight); LOGD("\tinkjet: %d", printer_cap->inkjet); LOGD("\tresolutions supported:"); for (index = 0; index < printer_cap->numSupportedResolutions; index++) { LOGD("\t (%d dpi)", printer_cap->supportedResolutions[index]); } } LOGD("wprintGetCapabilities: Exit"); return result; } /* * Returns a preferred print format supported by the printer */ static char *_get_print_format(const char *mime_type, const wprint_job_params_t *job_params, const printer_capabilities_t *cap) { char *print_format = NULL; errno = OK; if (((strcmp(mime_type, MIME_TYPE_PDF) == 0) && cap->canPrintPDF)) { // For content type=photo and a printer that supports both PCLm and PDF, // prefer PCLm over PDF. if (job_params && (strcasecmp(job_params->docCategory, "photo") == 0) && cap->canPrintPCLm) { print_format = PRINT_FORMAT_PCLM; LOGI("_get_print_format(): print_format switched from PDF to PCLm"); } else { print_format = PRINT_FORMAT_PDF; } } else if (cap->canPrintPCLm || cap->canPrintPDF) { // PCLm is a subset of PDF print_format = PRINT_FORMAT_PCLM; #if (USE_PWG_OVER_PCLM != 0) if (cap->canPrintPWG) { print_format = PRINT_FORMAT_PWG; } #endif // (USE_PWG_OVER_PCLM != 0) } else if (cap->canPrintPWG) { print_format = PRINT_FORMAT_PWG; } else { errno = EBADRQC; } if (print_format != NULL) { LOGI("\t_get_print_format(): print_format: %s", print_format); } return print_format; } status_t wprintGetDefaultJobParams(wprint_job_params_t *job_params) { status_t result = ERROR; static const wprint_job_params_t _default_job_params = {.print_format = _DEFAULT_PRINT_FORMAT, .pcl_type = _DEFAULT_PCL_TYPE, .media_size = US_LETTER, .media_type = MEDIA_PLAIN, .duplex = DUPLEX_MODE_NONE, .dry_time = DUPLEX_DRY_TIME_NORMAL, .color_space = COLOR_SPACE_COLOR, .media_tray = TRAY_SRC_AUTO_SELECT, .pixel_units = DEFAULT_RESOLUTION, .render_flags = 0, .num_copies =1, .borderless = false, .cancelled = false, .renderInReverseOrder = false, .ipp_1_0_supported = false, .ipp_2_0_supported = false, .epcl_ipp_supported = false, .strip_height = STRIPE_HEIGHT, .docCategory = {0}, .copies_supported = false}; if (job_params == NULL) return result; memcpy(job_params, &_default_job_params, sizeof(_default_job_params)); return OK; } status_t wprintGetFinalJobParams(wprint_job_params_t *job_params, const printer_capabilities_t *printer_cap) { int i; status_t result = ERROR; float margins[NUM_PAGE_MARGINS]; if (job_params == NULL) { return result; } result = OK; job_params->accepts_pclm = printer_cap->canPrintPCLm; job_params->accepts_pdf = printer_cap->canPrintPDF; job_params->media_default = printer_cap->mediaDefault; if (printer_cap->ePclIppVersion == 1) { job_params->epcl_ipp_supported = true; } if (printer_cap->canCopy) { job_params->copies_supported = true; } if (printer_cap->ippVersionMajor == 2) { job_params->ipp_1_0_supported = true; job_params->ipp_2_0_supported = true; } else if (printer_cap->ippVersionMajor == 1) { job_params->ipp_1_0_supported = true; job_params->ipp_2_0_supported = false; } if (!printer_cap->color) { job_params->color_space = COLOR_SPACE_MONO; } if (printer_cap->canPrintPCLm || printer_cap->canPrintPDF) { job_params->pcl_type = PCLm; #if (USE_PWG_OVER_PCLM != 0) if ( printer_cap->canPrintPWG) { job_params->pcl_type = PCLPWG; } #endif // (USE_PWG_OVER_PCLM != 0) } else if (printer_cap->canPrintPWG) { job_params->pcl_type = PCLPWG; } LOGD("wprintGetFinalJobParams: Using PCL Type %s", getPCLTypeString(job_params->pcl_type)); // set strip height job_params->strip_height = printer_cap->stripHeight; // make sure the number of copies is valid if (job_params->num_copies <= 0) { job_params->num_copies = 1; } // If printing photo and HIGH quality is supported, specify it. if (strcasecmp(job_params->docCategory, "photo") == 0 && int_array_contains( printer_cap->supportedQuality, printer_cap->numSupportedQuality, IPP_QUALITY_HIGH)) { job_params->print_quality = IPP_QUALITY_HIGH; } // confirm that the media size is supported for (i = 0; i < printer_cap->numSupportedMediaSizes; i++) { if (job_params->media_size == printer_cap->supportedMediaSizes[i]) { break; } } if (i >= printer_cap->numSupportedMediaSizes) { job_params->media_size = ISO_A4; job_params->media_tray = TRAY_SRC_AUTO_SELECT; } // check that we support the media tray for (i = 0; i < printer_cap->numSupportedMediaTrays; i++) { if (job_params->media_tray == printer_cap->supportedMediaTrays[i]) { break; } } // media tray not supported, default to automatic if (i >= printer_cap->numSupportedMediaTrays) { job_params->media_tray = TRAY_SRC_AUTO_SELECT; } if (printer_cap->isMediaSizeNameSupported == true) { job_params->media_size_name = true; } else { job_params->media_size_name = false; } // verify borderless setting if ((job_params->borderless == true) && !printer_cap->borderless) { job_params->borderless = false; } // borderless and margins don't get along if (job_params->borderless && ((job_params->job_top_margin > 0.0f) || (job_params->job_left_margin > 0.0f) || (job_params->job_right_margin > 0.0f) || (job_params->job_bottom_margin > 0.0f))) { job_params->borderless = false; } // verify duplex setting if ((job_params->duplex != DUPLEX_MODE_NONE) && !printer_cap->duplex) { job_params->duplex = DUPLEX_MODE_NONE; } // borderless and duplex don't get along either if (job_params->borderless && (job_params->duplex != DUPLEX_MODE_NONE)) { job_params->duplex = DUPLEX_MODE_NONE; } if ((job_params->duplex == DUPLEX_MODE_BOOK) && !printer_cap->canRotateDuplexBackPage) { job_params->render_flags |= RENDER_FLAG_ROTATE_BACK_PAGE; } if (job_params->render_flags & RENDER_FLAG_ROTATE_BACK_PAGE) { LOGD("wprintGetFinalJobParams: Duplex is on and device needs back page rotated."); } if ((job_params->duplex == DUPLEX_MODE_NONE) && !printer_cap->faceDownTray) { job_params->renderInReverseOrder = true; } else { job_params->renderInReverseOrder = false; } if (job_params->render_flags & RENDER_FLAG_AUTO_SCALE) { job_params->render_flags |= AUTO_SCALE_RENDER_FLAGS; } else if (job_params->render_flags & RENDER_FLAG_AUTO_FIT) { job_params->render_flags |= AUTO_FIT_RENDER_FLAGS; } job_params->pixel_units = _findCloseResolutionSupported(DEFAULT_RESOLUTION, MAX_SUPPORTED_RESOLUTION, printer_cap); printable_area_get_default_margins(job_params, printer_cap, &margins[TOP_MARGIN], &margins[LEFT_MARGIN], &margins[RIGHT_MARGIN], &margins[BOTTOM_MARGIN]); printable_area_get(job_params, margins[TOP_MARGIN], margins[LEFT_MARGIN], margins[RIGHT_MARGIN], margins[BOTTOM_MARGIN]); job_params->accepts_app_name = printer_cap->docSourceAppName; job_params->accepts_app_version = printer_cap->docSourceAppVersion; job_params->accepts_os_name = printer_cap->docSourceOsName; job_params->accepts_os_version = printer_cap->docSourceOsVersion; return result; } wJob_t wprintStartJob(const char *printer_addr, port_t port_num, const wprint_job_params_t *job_params, const printer_capabilities_t *printer_cap, const char *mime_type, const char *pathname, wprint_status_cb_t cb_fn, const char *debugDir, const char *scheme) { wJob_t job_handle = WPRINT_BAD_JOB_HANDLE; _msg_t msg; struct stat stat_buf; bool is_dir = false; _job_queue_t *jq; wprint_plugin_t *plugin = NULL; char *print_format; ifc_print_job_t *print_ifc; if (mime_type == NULL) { errno = EINVAL; return job_handle; } print_format = _get_print_format(mime_type, job_params, printer_cap); if (print_format == NULL) return job_handle; // check to see if we have an appropriate plugin if (OK == stat(pathname, &stat_buf)) { if (S_ISDIR(stat_buf.st_mode)) { is_dir = true; } else if (stat_buf.st_size == 0) { errno = EBADF; return job_handle; } } else { errno = ENOENT; return job_handle; } // Make sure we have job_params if (job_params == NULL) { errno = ECOMM; return job_handle; } plugin = plugin_search(mime_type, print_format); _lock(); if (plugin) { job_handle = _get_handle(); if (job_handle == WPRINT_BAD_JOB_HANDLE) { errno = EAGAIN; } } else { errno = ENOSYS; LOGE("wprintStartJob(): ERROR: no plugin found for %s => %s", mime_type, print_format); } if (job_handle != WPRINT_BAD_JOB_HANDLE) { print_ifc = (ifc_print_job_t *) _get_print_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP)); // fill out the job queue record jq = _get_job_desc(job_handle); if (jq == NULL) { _recycle_handle(job_handle); job_handle = WPRINT_BAD_JOB_HANDLE; _unlock(); return job_handle; } if (debugDir != NULL) { strncpy(jq->debug_path, debugDir, MAX_PATHNAME_LENGTH); jq->debug_path[MAX_PATHNAME_LENGTH] = 0; } strncpy(jq->printer_addr, printer_addr, MAX_PRINTER_ADDR_LENGTH); strncpy(jq->mime_type, mime_type, MAX_MIME_LENGTH); strncpy(jq->pathname, pathname, MAX_PATHNAME_LENGTH); jq->port_num = port_num; jq->cb_fn = cb_fn; jq->print_ifc = print_ifc; jq->cancel_ok = true; // assume cancel is ok jq->plugin = plugin; memcpy(jq->printer_uri, printer_cap->httpResource, MIN(ARRAY_SIZE(printer_cap->httpResource), ARRAY_SIZE(jq->printer_uri))); jq->status_ifc = _get_status_ifc(((port_num == 0) ? PORT_FILE : PORT_IPP)); memcpy((char *) &(jq->job_params), job_params, sizeof(wprint_job_params_t)); jq->use_secure_uri = (strstr(scheme, IPPS_PREFIX) != NULL); size_t useragent_len = strlen(USERAGENT_PREFIX) + strlen(jq->job_params.docCategory) + 1; char *useragent = (char *) malloc(useragent_len); if (useragent != NULL) { snprintf(useragent, useragent_len, USERAGENT_PREFIX "%s", jq->job_params.docCategory); jq->job_params.useragent = useragent; } // Make a copy of the job_params certificate if it is present if (job_params->certificate) { jq->job_params.certificate = malloc(job_params->certificate_len); if (jq->job_params.certificate) { memcpy(jq->job_params.certificate, job_params->certificate, job_params->certificate_len); } } jq->job_params.page_num = 0; jq->job_params.print_format = print_format; if (strcmp(print_format, PRINT_FORMAT_PCLM) == 0) { if (printer_cap->canPrintPCLm || printer_cap->canPrintPDF) { jq->job_params.pcl_type = PCLm; } else { jq->job_params.pcl_type = PCLNONE; } } if (strcmp(print_format, PRINT_FORMAT_PWG) == 0) { if (printer_cap->canPrintPWG) { jq->job_params.pcl_type = PCLPWG; } else { jq->job_params.pcl_type = PCLNONE; } } // if the pathname is a directory, then this is a multi-page job with individual pages if (is_dir) { jq->is_dir = true; jq->num_pages = 0; // create a pageQ for queuing page information jq->pageQ = msgQCreate(_MAX_PAGES_PER_JOB, sizeof(_page_t)); // create a secondary page Q for subsequently saving page data for copies #2 to n if (jq->job_params.num_copies > 1) { jq->saveQ = msgQCreate(_MAX_PAGES_PER_JOB, sizeof(_page_t)); } } else { jq->num_pages = 1; } // post a message with job_handle to the msgQ that is serviced by a thread msg.id = MSG_RUN_JOB; msg.job_id = job_handle; if (print_ifc && plugin && plugin->print_page && (msgQSend(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT, MSG_Q_FIFO) == OK)) { errno = OK; LOGD("wprintStartJob(): print job %ld queued (%s => %s)", job_handle, mime_type, print_format); } else { if (print_ifc == NULL) { errno = EAFNOSUPPORT; } else if ((plugin == NULL) || (plugin->print_page == NULL)) { errno = ELIBACC; } else { errno = EBADMSG; } LOGE("wprintStartJob(): ERROR plugin->start_job(%ld) : %s => %s", job_handle, mime_type, print_format); jq->job_state = JOB_STATE_ERROR; _recycle_handle(job_handle); job_handle = WPRINT_BAD_JOB_HANDLE; } } _unlock(); return job_handle; } status_t wprintEndJob(wJob_t job_handle) { _page_t page; _job_queue_t *jq; status_t result = ERROR; _lock(); jq = _get_job_desc(job_handle); if (jq) { // if the job is done and is to be freed, do it if ((jq->job_state == JOB_STATE_CANCELLED) || (jq->job_state == JOB_STATE_ERROR) || (jq->job_state == JOB_STATE_CORRUPTED) || (jq->job_state == JOB_STATE_COMPLETED)) { result = OK; if (jq->pageQ) { while ((msgQNumMsgs(jq->pageQ) > 0) && (msgQReceive(jq->pageQ, (char *) &page, sizeof(page), WAIT_FOREVER) == OK)) { } result |= msgQDelete(jq->pageQ); jq->pageQ = NULL; } if (jq->saveQ) { while ((msgQNumMsgs(jq->saveQ) > 0) && (msgQReceive(jq->saveQ, (char *) &page, sizeof(page), WAIT_FOREVER) == OK)) { } result |= msgQDelete(jq->saveQ); jq->saveQ = NULL; } _recycle_handle(job_handle); } else { LOGE("job %ld cannot be ended from state %d", job_handle, jq->job_state); } } else { LOGE("ERROR: wprintEndJob(%ld), job not found", job_handle); } _unlock(); return result; } status_t wprintPage(wJob_t job_handle, int page_num, const char *filename, bool last_page, bool pdf_page, unsigned int top_margin, unsigned int left_margin, unsigned int right_margin, unsigned int bottom_margin) { _job_queue_t *jq; _page_t page; status_t result = ERROR; struct stat stat_buf; _lock(); jq = _get_job_desc(job_handle); // use empty string to indicate EOJ for an empty job if (!filename) { filename = ""; last_page = true; } else if (OK == stat(filename, &stat_buf)) { if (!S_ISREG(stat_buf.st_mode) || (stat_buf.st_size == 0)) { _unlock(); return result; } } else { _unlock(); return result; } // must be setup as a multi-page job, page_num must be valid, and filename must fit if (jq && jq->is_dir && !(jq->last_page_seen) && (((strlen(filename) < MAX_PATHNAME_LENGTH)) || (jq && (strcmp(filename, "") == 0) && last_page))) { memset(&page, 0, sizeof(page)); page.page_num = page_num; page.corrupted = false; page.pdf_page = pdf_page; page.last_page = last_page; page.top_margin = top_margin; page.left_margin = left_margin; page.right_margin = right_margin; page.bottom_margin = bottom_margin; if ((strlen(filename) == 0) || strchr(filename, '/')) { // assume empty or complete pathname and use it as it is strncpy(page.filename, filename, MAX_PATHNAME_LENGTH); } else { // generate a complete pathname snprintf(page.filename, MAX_PATHNAME_LENGTH, "%s/%s", jq->pathname, filename); } if (last_page) { jq->last_page_seen = true; } result = msgQSend(jq->pageQ, (char *) &page, sizeof(page), NO_WAIT, MSG_Q_FIFO); } if (result == OK) { LOGD("wprintPage(%ld, %d, %s, %d)", job_handle, page_num, filename, last_page); if (!(last_page && (strcmp(filename, "") == 0))) { jq->num_pages++; } } else { LOGE("wprintPage(%ld, %d, %s, %d)", job_handle, page_num, filename, last_page); } _unlock(); return result; } status_t wprintCancelJob(wJob_t job_handle) { _job_queue_t *jq; status_t result; _lock(); jq = _get_job_desc(job_handle); if (jq) { LOGI("received cancel request"); // send a dummy page in case we're waiting on the msgQ page receive if ((jq->job_state == JOB_STATE_RUNNING) || (jq->job_state == JOB_STATE_BLOCKED)) { bool enableTimeout = true; jq->cancel_ok = true; jq->job_params.cancelled = true; wprintPage(job_handle, jq->num_pages + 1, NULL, true, false, 0, 0, 0, 0); if (jq->status_ifc) { // are we blocked waiting for the job to start if ((jq->job_state != JOB_STATE_BLOCKED) || (jq->job_params.page_num != 0)) { errno = OK; jq->cancel_ok = ((jq->status_ifc->cancel)(jq->status_ifc, jq->job_params.job_originating_user_name) == 0); if ((jq->cancel_ok == true) && (errno != OK)) { enableTimeout = false; } } } if (!jq->cancel_ok) { LOGE("CANCEL did not go through or is not supported for this device"); enableTimeout = true; } if (enableTimeout && (jq->print_ifc != NULL) && (jq->print_ifc->enable_timeout != NULL)) { jq->print_ifc->enable_timeout(jq->print_ifc, 1); } errno = (jq->cancel_ok ? OK : ENOTSUP); jq->job_state = JOB_STATE_CANCEL_REQUEST; result = OK; } else if ((jq->job_state == JOB_STATE_CANCEL_REQUEST) || (jq->job_state == JOB_STATE_CANCELLED)) { result = OK; errno = (jq->cancel_ok ? OK : ENOTSUP); } else if (jq->job_state == JOB_STATE_QUEUED) { jq->job_params.cancelled = true; jq->job_state = JOB_STATE_CANCELLED; if (jq->cb_fn) { wprint_job_callback_params_t cb_param; cb_param.state = JOB_DONE; cb_param.blocked_reasons = BLOCKED_REASONS_CANCELLED; cb_param.job_done_result = CANCELLED; cb_param.certificate = jq->certificate; cb_param.certificate_len = jq->certificate_len; jq->cb_fn(job_handle, (void *) &cb_param); } errno = OK; result = OK; } else { LOGE("job in other state"); result = ERROR; errno = EBADRQC; } } else { LOGE("could not find job"); result = ERROR; errno = EBADR; } _unlock(); return result; } status_t wprintExit(void) { _msg_t msg; if (_msgQ) { // toss the remaining messages in the msgQ while ((msgQNumMsgs(_msgQ) > 0) && (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT))) {} // send a quit message msg.id = MSG_QUIT; msgQSend(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT, MSG_Q_FIFO); // stop the job thread _stop_thread(); // empty out the semaphore while (sem_trywait(&_job_end_wait_sem) == OK); while (sem_trywait(&_job_start_wait_sem) == OK); // receive any messages just in case while ((msgQNumMsgs(_msgQ) > 0) && (OK == msgQReceive(_msgQ, (char *) &msg, sizeof(msg), NO_WAIT))) {} // delete the msgQ msgQDelete(_msgQ); _msgQ = NULL; sem_destroy(&_job_end_wait_sem); sem_destroy(&_job_start_wait_sem); pthread_mutex_destroy(&_q_lock); } return OK; } void wprintSetSourceInfo(const char *appName, const char *appVersion, const char *osName) { if (appName) { strncpy(g_appName, appName, (sizeof(g_appName) - 1)); } if (appVersion) { strncpy(g_appVersion, appVersion, (sizeof(g_appVersion) - 1)); } if (osName) { strncpy(g_osName, osName, (sizeof(g_osName) - 1)); } LOGI("App Name: '%s', Version: '%s', OS: '%s'", g_appName, g_appVersion, g_osName); }