/* * Higher level bjnp IO communication implementation for * bjnp backend for the Common UNIX Printing System (CUPS). * Copyright 2014 by Louis Lagendijk * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 or later. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include "bjnp.h" #include "bjnp-protocol.h" #include "bjnp-io.h" #include "bjnp-commands.h" /* * new_printer - allocate printer struct and set defaults */ static printer_t *new_printer(void) { int i; printer_t *printer = malloc(sizeof(printer_t)); memset(printer, 0, sizeof(*printer)); printer->global_ink_warning_level = LEVEL_UNKNOWN; printer->paper_out = -1; printer->first_output = 1; printer->reporting_capabilities = BJNP_REPORT_ALL; for (i = 0; i < BJNP_CARTRIDGES_MAX; i++) { printer->cartridges[i].cart_index = -1; printer->cartridges[i].warning = LEVEL_UNKNOWN; printer->cartridges[i].marker_level = -1; } printer->no_cartridges = -1; printer->last_level_report = 0; printer->fd = -1; return printer; } void free_printer(printer_t *printer) { free(printer); } /* * Close printer and cleanup & free the structure */ void bjnp_close_printer(printer_t *printer) { /* * Signal end of printjob to printer */ if (printer->fd >= 0) { close(printer->fd); } bjnp_debug(LOG_DEBUG2, "Finish printjob\n"); bjnp_send_close(printer); free_printer(printer); } /* * bjnp_tcp_connect - Setup a TCP connection to the printer. * */ static int bjnp_tcp_connect(printer_t *printer) { int val; int tcp_socket = -1; #ifdef __APPLE__ struct timeval timeout; #endif /* __APPLE__ */ char host[BJNP_HOST_MAX]; char family[BJNP_FAMILY_MAX]; int port; /* * Create the socket... */ if ((tcp_socket = (int)socket(get_protocol_family(printer->printer_sa), SOCK_STREAM, 0)) < 0) { return tcp_socket; } /* * Set options... */ val = 1; setsockopt(tcp_socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); #if 0 val = 1; setsockopt(tcp_socket, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)); val = 1; setsockopt(tcp_socket, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(val)); #endif #ifdef __APPLE__ /* * Use a 30-second read timeout when connecting to limit the amount of time * we block... */ timeout.tv_sec = 30; timeout.tv_usec = 0; setsockopt(tcp_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); #endif /* __APPLE__ */ /* * Using TCP_NODELAY improves responsiveness, especially on systems * with a slow loopback interface... */ val = 1; setsockopt(tcp_socket, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); /* * Close this socket when starting another process... */ fcntl(tcp_socket, F_SETFD, FD_CLOEXEC); /* * Then connect... */ get_printer_address_info(printer, host, &port, family); if (connect(tcp_socket, &(printer->printer_sa.addr), sa_size(printer->printer_sa)) != 0) { bjnp_debug(LOG_CRIT, "Failed to establish TCP connection to printer %s port %d\n", host, port); close(tcp_socket); return -1; } bjnp_debug(LOG_DEBUG, "Established TCP connection (fd = %d) to printer %s port %d\n", tcp_socket, host, port); return tcp_socket; } static printer_t * bjnp_init_printer(printer_t *printer, http_addr_t *addr, const char *user, const char *title) { uint16_t session_id; char host[BJNP_HOST_MAX]; int port; char family[BJNP_FAMILY_MAX]; get_address_info(addr, host, &port, family); bjnp_debug(LOG_DEBUG, "Connecting to %s port %d (%s)\n", host, port, family); if ((session_id = bjnp_send_job_details(addr, user, title)) < 0) { /* printer init failed, release struct */ if (printer != NULL) { bjnp_close_printer(printer); } return NULL; } /* found printer, now fill the printer struct */ if (printer == NULL) { printer = new_printer(); } printer->io_free = 1; printer->session_id = session_id; memcpy(&(printer->printer_sa), addr, sa_size(printer->printer_sa)); /* set printer information in case it is needed later */ get_printer_id(&(printer->printer_sa), printer->model, printer->IEEE1284_id); return printer; } static printer_t * bjnp_start_job(printer_t *printer, http_addrlist_t *list, const char *user, const char *title) { /* * send details of printjob to printer * Returns: addrlist set to address details of used printer */ http_addr_t *addr; if (printer == NULL) { while (list != NULL) { addr = (http_addr_t *) & (list->addr); if ((printer = bjnp_init_printer(printer, addr, user, title)) != NULL) { return printer; } list = list->next; } } else { /* we already found the printer, but could not connect to it */ printer = bjnp_init_printer(printer, &(printer->printer_sa), user, title); } return printer; } printer_t * bjnp_printer_connect(printer_t *printer, http_addrlist_t *list, const char *user, const char *title, int *bjnp_error) { /* look for printer address and tell printer that we want to start printing */ if ((printer = bjnp_start_job(printer, list, user, title)) != NULL) { /* found printer, check resources & state */ if ((*bjnp_error = bjnp_report_levels(printer)) != 0) { /* printer not ready, we keep the printer struct around, */ /* but tell the printer to close the job. */ /* This avoids cluttering the printing log */ bjnp_send_close(printer); } else { /* printer is ready, setup tcp-connection */ /* this should succeed */ if ((printer->fd = bjnp_tcp_connect(printer)) < 0) { /* remove the printer struct as we did fail */ bjnp_close_printer(printer); printer = NULL; } } } return printer; } ssize_t bjnp_write2(printer_t *printer, const void *buf, size_t count) { /* * This function writes printdata to the printer. This function mimicks the std. * lib. write function as much as possible. * Returns: number of bytes written to the printer */ int sent_bytes; int terrno; int command_len; if (!printer->io_free) { errno = EAGAIN; return -1; } /* set BJNP command header */ command_len = bjnp_header_size + count; printer->seq_no = bjnp_printer_set_command_header(printer, &(printer->print_buf), CMD_TCP_PRINT, command_len); printer->io_count = count; memcpy(&(printer->print_buf.tcp_print.data), buf, count); bjnp_debug(LOG_DEBUG, "bjnp_write2: printing %d bytes\n", count); bjnp_hexdump(LOG_DEBUG2, "Print data:", (char *) & (printer->print_buf), command_len); if ((sent_bytes = write(printer->fd, &(printer->print_buf), command_len)) < 0) { /* return result from write */ terrno = errno; bjnp_debug(LOG_CRIT, "bjnp_write2: Could not send data!\n"); errno = terrno; return sent_bytes; } else if (sent_bytes != command_len) { errno = EIO; return -1; } printer->io_free = 0; /* correct nr of bytes sent for length of command */ /* sent_byte < expected is an unrecoverable error */ return sent_bytes - bjnp_header_size; } int bjnp_backchannel(printer_t *printer, ssize_t *written) { /* * This function receives the responses to the write commands. * written wil be set to the number of bytes confirmed by the printer * Returns: * BJNP_OK when valid ack is received, written is set to number of bytes * sent to and accepted by printer (could be 0 for keep-alive) * BJNP_IO_ERROR when any io-error occurred * BJNP_NOT_AN_ACK when the packet received was not an ack, must be ignored * BJNP_THROTTLE when printer indicated it could not handle the input data */ bjnp_response_t response; unsigned int recv_bytes; unsigned int resp_seqno; int terrno; uint32_t payload_len; fd_set input; struct timeval timeout; bjnp_debug(LOG_DEBUG, "bjnp_backchannel: receiving response on fd = %d\n", printer->fd); /* get response header, we can unfortunately not */ /* rely on getting the payload in the first read */ if ((recv_bytes = read(printer->fd, &response, bjnp_header_size)) < bjnp_header_size) { terrno = errno; bjnp_debug(LOG_CRIT, "bjnp_backchannel: (recv) could not read response header, received %d bytes!\n", recv_bytes); if (terrno < 0) { bjnp_debug(LOG_CRIT, "bjnp_backchannel: (recv) error: %s!\n", strerror(terrno)); } errno = terrno; return BJNP_IO_ERROR; } /* got response header back, get payload length */ payload_len = ntohl(response.tcp_print_response.header.payload_len); /* it should be at least the same size as the accepted field */ if ((payload_len >= sizeof(response.tcp_print_response.accepted)) && (payload_len < (sizeof(response) - sizeof(struct bjnp_header)))) { /* read nr of bytes accepted by printer */ FD_ZERO(&input); FD_SET(printer->fd, &input); timeout.tv_sec = 1; timeout.tv_usec = 0; if (select(printer->fd + 1, &input, NULL, NULL, &timeout) <= 0) { terrno = errno; bjnp_debug(LOG_CRIT, "bjnp_backchannel: could not read response payload (select)!\n"); errno = terrno; return BJNP_IO_ERROR; } if ((recv_bytes = read(printer->fd, &response.tcp_print_response.accepted, payload_len)) < payload_len) { terrno = errno; bjnp_debug(LOG_CRIT, "bjnp_backchannel: could not read response payload (recv)!\n"); errno = terrno; return BJNP_IO_ERROR; } *written = ntohl(response.tcp_print_response.accepted); } else { /* there is no payload, assume 0 bytes received */ *written = 0; errno = EIO; return BJNP_IO_ERROR; } bjnp_hexdump(LOG_DEBUG2, "TCP response:", &response, bjnp_header_size + recv_bytes); if (response.tcp_print_response.header.cmd_code != CMD_TCP_PRINT) { /* not a print response, discard */ bjnp_debug(LOG_DEBUG, "Not a printing response packet, discarding!"); return BJNP_NOT_AN_ACK; } resp_seqno = ntohs(response.header.seq_no); /* do sanity check on sequence number of response */ if (resp_seqno != printer->seq_no) { bjnp_debug(LOG_CRIT, "bjnp_backchannel: printer reported sequence number %d, expected %d\n", resp_seqno, printer->seq_no); errno = EIO; return BJNP_IO_ERROR; } bjnp_debug(LOG_DEBUG, "bjnp_backchannel: response: written = %lx, seqno = %lx\n", *written, resp_seqno); printer->io_free = 1; /* check length reported by printer */ if (printer->io_count == *written) { /* printer reported expected number of bytes */ return BJNP_OK; } else if (*written == 0) { /* data was sent to printer, but printer reports that it is busy */ /* add a delay before we try again */ bjnp_debug(LOG_INFO, "Printer does not accept data, throttling....\n"); usleep(40000); return BJNP_THROTTLE; } /* printer reports unexpected number of bytes */ bjnp_debug(LOG_CRIT, "bjnp_backchannel: printer reported %d bytes received, expected %d\n", written, printer->io_count); errno = EIO; return BJNP_IO_ERROR; } ssize_t bjnp_write(printer_t *printer, const void *buf, size_t count) { /* This is a wrapper around bjnp_write2. It parses the input stream for * BJL commands and outputs these in a new/separate tcp packet. Each call * prints at most buffer upto next command * It is an ugly hack, I know.... * This function can also be used to send keep-alive packets when count = 0 */ char start_cmd[] = { 0x1b, 0x5b, 0x4b, 0x2, 0x0, 0x0 }; int print_count; int result; int terrno; /* TODO: allow scanning over buffer borders */ bjnp_debug(LOG_DEBUG, "bjnp_write: starting printing of %d characters\n", count); if ((print_count = find_bin_string(buf, count, start_cmd, sizeof(start_cmd))) == -1) { /* no command found, print whole buffer */ print_count = count; } /* print content of buf upto command */ result = bjnp_write2(printer, buf, print_count); terrno = errno; bjnp_debug(LOG_DEBUG, "bjnp_write: Printed %d bytes, last command sent: %d\n", result, printer->seq_no); errno = terrno; return result; } int bjnp_backendGetDeviceID(printer_t *printer, char *device_id, int device_id_size, char *make_model, int make_model_size) { /* * Returns the printer information for the active printer * Returns: 0 if ok * -1 if not found */ strncpy(device_id, printer->IEEE1284_id, device_id_size); device_id[device_id_size] = '\0'; strncpy(make_model, printer->model, make_model_size); make_model[make_model_size] = '\0'; if ((strlen(make_model) == 0) && (strlen(device_id) == 0)) { return -1; } return 0; } int bjnp_get_device_fd(printer_t *printer) { return printer->fd; }