blob: df177bb4a3b35bfb9d866fbcd0a71486dcda52ae [file] [log] [blame]
/*
* "$Id: pap.c 7010 2007-10-10 21:08:51Z mike $"
*
* © Copyright 2004 Apple Computer, Inc. All rights reserved.
*
* IMPORTANT: This Apple software is supplied to you by Apple Computer,
* Inc. ("Apple") in consideration of your agreement to the following
* terms, and your use, installation, modification or redistribution of
* this Apple software constitutes acceptance of these terms. If you do
* not agree with these terms, please do not use, install, modify or
* redistribute this Apple software.
*
* In consideration of your agreement to abide by the following terms, and
* subject to these terms, Apple grants you a personal, non-exclusive
* license, under AppleÕs copyrights in this original Apple software (the
* "Apple Software"), to use, reproduce, modify and redistribute the Apple
* Software, with or without modifications, in source and/or binary forms;
* provided that if you redistribute the Apple Software in its entirety and
* without modifications, you must retain this notice and the following
* text and disclaimers in all such redistributions of the Apple Software.
* Neither the name, trademarks, service marks or logos of Apple Computer,
* Inc. may be used to endorse or promote products derived from the Apple
* Software without specific prior written permission from Apple. Except
* as expressly stated in this notice, no other rights or licenses, express
* or implied, are granted by Apple herein, including but not limited to
* any patent rights that may be infringed by your derivative works or by
* other works in which the Apple Software may be incorporated.
*
* The Apple Software is provided by Apple on an "AS IS" basis. APPLE
* MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
* OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
*
* IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
* MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
* AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
* STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*
* This program implements the Printer Access Protocol (PAP) on top of AppleTalk
* Transaction Protocol (ATP). If it were to use the blocking pap functions of
* the AppleTalk library it would need seperate threads for reading, writing
* and status.
*
* Contents:
*
* main() - Send a file to the specified Appletalk printer.
* listDevices() - List all LaserWriter printers in the local zone.
* printFile() - Print file.
* papOpen() - Open a pap session to a printer.
* papClose() - Close a pap session.
* papWrite() - Write bytes to a printer.
* papCloseResp() - Send a pap close response.
* papSendRequest() - Fomrat and send a pap packet.
* papCancelRequest() - Cancel a pending pap request.
* sidechannel_request() - Handle side-channel requests.
* statusUpdate() - Print printer status to stderr.
* parseUri() - Extract the print name and zone from a uri.
* addPercentEscapes() - Encode a string with percent escapes.
* removePercentEscapes - Remove percent escape sequences from a string.
* nbptuple_compare() - Compare routine for qsort.
* okayToUseAppleTalk() - Returns true if AppleTalk is available and enabled.
* packet_name() - Returns packet name string.
* connectTimeout() - Returns the connect timeout preference value.
* signalHandler() - handle SIGINT to close the session before quiting.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <termios.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <sys/fcntl.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <netat/appletalk.h>
#include <netat/atp.h>
#include <netat/ddp.h>
#include <netat/nbp.h>
#include <netat/pap.h>
#include <cups/cups.h>
#include <cups/backend.h>
#include <cups/sidechannel.h>
#include <cups/i18n.h>
#include <libkern/OSByteOrder.h>
#ifdef HAVE_APPLETALK_AT_PROTO_H
# include <AppleTalk/at_proto.h>
#else
/* These definitions come from at_proto.h... */
# define ZIP_DEF_INTERFACE NULL
enum { RUNNING, NOTLOADED, LOADED, OTHERERROR };
extern int atp_abort(int fd, at_inet_t *dest, u_short tid);
extern int atp_close(int fd);
extern int atp_getreq(int fd, at_inet_t *src, char *buf, int *len, int *userdata,
int *xo, u_short *tid, u_char *bitmap, int nowait);
extern int atp_getresp(int fd, u_short *tid, at_resp_t *resp);
extern int atp_look(int fd);
extern int atp_open(at_socket *sock);
extern int atp_sendreq(int fd, at_inet_t *dest, char *buf, int len,
int userdata, int xo, int xo_relt, u_short *tid,
at_resp_t *resp, at_retry_t *retry, int nowait);
extern int atp_sendrsp(int fd, at_inet_t *dest, int xo, u_short tid,
at_resp_t *resp);
extern int checkATStack();
extern int nbp_lookup(at_entity_t *entity, at_nbptuple_t *buf, int max,
at_retry_t *retry);
extern int nbp_make_entity(at_entity_t *entity, char *obj, char *type,
char *zone);
extern int zip_getmyzone(char *ifName, at_nvestr_t *zone);
#endif /* HAVE_APPLETALK_AT_PROTO_H */
#include <CoreFoundation/CFURL.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFPreferences.h>
/* Defines */
#define MAX_PRINTERS 500 /* Max number of printers we can lookup */
#define PAP_CONNID 0
#define PAP_TYPE 1
#define PAP_EOF 2
#define CONNID_OF(p) (((u_char *)&p)[0])
#define TYPE_OF(p) (((u_char *)&p)[1])
#define SEQUENCE_NUM(p) (((u_char *)&p)[2])
#define IS_PAP_EOF(p) (((u_char *)&p)[2])
#ifndef true
#define true 1
#define false 0
#endif
/* Globals */
int gSockfd = 0; /* Socket descriptor */
at_inet_t gSessionAddr = { 0 }; /* Address of the session responding socket */
u_char gConnID = 0; /* PAP session connection id */
u_short gSendDataID = 0; /* Transaction id of pending send-data request */
u_short gTickleID = 0; /* Transaction id of outstanding tickle request*/
int gWaitEOF = false; /* Option: wait for a remote's EOF */
int gStatusInterval= 5; /* Option: 0=off else seconds between status requests*/
int gErrorlogged = false; /* If an error was logged don't send any more INFO messages */
int gDebug = 0; /* Option: emit debugging info */
/* Local functions */
static int listDevices(void);
static int printFile(char* name, char* type, char* zone, int fdin, int fdout,
int fderr, int copies, int argc);
static int papOpen(at_nbptuple_t* tuple, u_char* connID, int* fd,
at_inet_t* pap_to, u_char* flowQuantum);
static int papClose();
static int papWrite(int sockfd, at_inet_t* dest, u_short tid, u_char connID,
u_char flowQuantum, char* data, int len, int eof);
static int papCloseResp(int sockfd, at_inet_t* dest, int xo, u_short tid,
u_char connID);
static int papSendRequest(int sockfd, at_inet_t* dest, u_char connID,
int function, u_char bitmap, int xo, int seqno);
static int papCancelRequest(int sockfd, u_short tid);
static void sidechannel_request();
static void statusUpdate(char* status, u_char statusLen);
static int parseUri(const char* argv0, char* name, char* type, char* zone);
static int addPercentEscapes(const char* src, char* dst, int dstMax);
static int removePercentEscapes(const char* src, char* dst, int dstMax);
static int nbptuple_compare(const void *p1, const void *p2);
static int okayToUseAppleTalk(void);
static const char *packet_name(u_char x);
static int connectTimeout(void);
static void signalHandler(int sigraised);
/*!
* @function main
* @abstract Send a file to the specified AppleTalk PAP address.
*
* Usage: printer-uri job-id user title copies options [file]
*
* @param argc # of arguments
* @param argv array of arguments
*
* @result A non-zero return value for errors
*/
int main (int argc, const char * argv[])
{
int err = 0;
FILE *fp; /* Print file */
int copies; /* Number of copies to print */
char name[NBP_NVE_STR_SIZE + 1]; /* +1 for a nul */
char type[NBP_NVE_STR_SIZE + 1]; /* +1 for a nul */
char zone[NBP_NVE_STR_SIZE + 1]; /* +1 for a nul */
/* Make sure status messages are not buffered... */
setbuf(stderr, NULL);
if (argc == 1 || (argc == 2 && strcmp(argv[1], "-discover") == 0))
{
listDevices();
return 0;
}
if (argc < 6 || argc > 7)
{
fprintf(stderr, "argc = %d\n", argc);
for (err = 0; err < argc; err++) {
fprintf(stderr, "%02d:%s\n", err, argv[err]);
}
fprintf(stderr, "Usage: pap job-id user title copies options [file]\n");
exit(EINVAL);
}
/* If we have 7 arguments, print the file named on the command-line.
* Otherwise, send stdin instead...
*/
if (argc == 6)
{
fp = stdin;
copies = 1;
}
else
{
fprintf(stderr, "DEBUG: opening print file \"%s\"\n", argv[6]);
/* Try to open the print file... */
if ((fp = fopen(argv[6], "rb")) == NULL)
{
_cupsLangPrintf(stderr,
_("ERROR: Unable to open print file \"%s\": %s\n"),
argv[6], strerror(errno));
return (1);
}
copies = atoi(argv[4]);
}
/* Extract the device name and options from the URI... */
parseUri(cupsBackendDeviceURI((char **)argv), name, type, zone);
err = printFile(name, type, zone, fileno(fp), STDOUT_FILENO, STDERR_FILENO, copies, argc);
if (fp != stdin)
fclose(fp);
/* Only clear the last status if there wasn't an error */
if (err == noErr && !gErrorlogged)
fprintf(stderr, "INFO:\n");
return err;
}
/*!
* @function listDevices
* @abstract Print a list of all LaserWriter type devices registered in the default zone.
*
* @result A non-zero return value for errors
*/
static int listDevices(void)
{
int err = noErr;
int i;
int numberFound;
at_nvestr_t at_zone;
at_entity_t entity;
at_nbptuple_t buf[MAX_PRINTERS];
at_retry_t retry;
char name[NBP_NVE_STR_SIZE+1];
char encodedName[(3 * NBP_NVE_STR_SIZE) + 1];
char zone[NBP_NVE_STR_SIZE+1];
char encodedZone[(3 * NBP_NVE_STR_SIZE) + 1];
/* Make sure it's okay to use appletalk */
if (!okayToUseAppleTalk())
{
fprintf(stderr, "INFO: AppleTalk disabled in System Preferences\n");
return -1; /* Network is down */
}
if ((err = zip_getmyzone(ZIP_DEF_INTERFACE, &at_zone)) != 0)
{
perror("ERROR: Unable to get default AppleTalk zone");
return -2;
}
memcpy(zone, at_zone.str, MIN(at_zone.len, sizeof(zone)-1));
zone[MIN(at_zone.len, sizeof(zone)-1)] = '\0';
fprintf(stderr, "INFO: Using default AppleTalk zone \"%s\"\n", zone);
addPercentEscapes(zone, encodedZone, sizeof(encodedZone));
/* Look up all the printers in our zone */
nbp_make_entity(&entity, "=", "LaserWriter", zone);
retry.retries = 1;
retry.interval = 1;
retry.backoff = 1;
if ((numberFound = nbp_lookup(&entity, buf, MAX_PRINTERS, &retry)) < 0)
{
perror("ERROR: Unable to lookup AppleTalk printers");
return numberFound;
}
if (numberFound >= MAX_PRINTERS)
fprintf(stderr, "WARNING: Adding only the first %d printers found", MAX_PRINTERS);
/* Not required but sort them so they look nice */
qsort(buf, numberFound, sizeof(at_nbptuple_t), nbptuple_compare);
for (i = 0; i < numberFound; i++)
{
memcpy(name, buf[i].enu_entity.object.str, MIN(buf[i].enu_entity.object.len, sizeof(name)-1));
name[MIN(buf[i].enu_entity.object.len, sizeof(name)-1)] = '\0';
if (addPercentEscapes(name, encodedName, sizeof(encodedName)) == 0)
{
/* Each line is of the form: "class URI "make model" "info" */
char make_model[128], /* Make and model */
*ptr;
if ((ptr = strchr(name, ' ')) != NULL)
{
/*
* If the printer name contains spaces, it is probably a make and
* model...
*/
if (!strncmp(name, "ET00", 4))
{
/*
* Drop leading ethernet address info...
*/
strlcpy(make_model, ptr + 1, sizeof(make_model));
}
else
strlcpy(make_model, name, sizeof(make_model));
}
else
strcpy(make_model, "Unknown");
printf("network pap://%s/%s/LaserWriter \"%s\" \"%s AppleTalk\"\n",
encodedZone, encodedName, make_model, name);
}
}
return numberFound;
}
/*!
* @function printFile
* @abstract Open a PAP session and send the data from the input socket to the printer.
*
* @param name NBP name
* @param zone NBP zone
* @param type NBP type
* @param fdin File descriptor to read data from
* @param fdout File descriptor to write printer responses to
* @param fderr File descriptor to write printer status to
* @param copies # of copies to send (in case in the converter couldn't handle this for us).
* @param argc # of command line arguments.
*
* @result A non-zero return value for errors
*/
static int printFile(char* name, char* type, char* zone, int fdin, int fdout, int fderr, int copies, int argc)
{
int err;
int rc;
int val;
int len, i;
char fileBuffer[4096]; /* File buffer */
int fileBufferNbytes;
off_t fileTbytes;
int fileEOFRead;
int fileEOFSent;
char sockBuffer[4096 + 1]; /* Socket buffer with room for nul */
char atpReqBuf[AT_PAP_DATA_SIZE];
fd_set readSet;
int use_sidechannel; /* Use side channel? */
at_nbptuple_t tuple;
at_inet_t sendDataAddr;
at_inet_t src;
at_resp_t resp;
int userdata, xo, reqlen;
u_short tid;
u_char bitmap;
int maxfdp1,
nbp_failures = 0;
struct timeval timeout, *timeoutPtr;
u_char flowQuantum = 1;
u_short recvSequence = 0;
time_t now,
start_time,
elasped_time,
sleep_time,
connect_timeout = -1,
nextStatusTime = 0;
at_entity_t entity;
at_retry_t retry;
#if defined(HAVE_SIGACTION) && !defined(HAVE_SIGSET)
struct sigaction action; /* Actions for POSIX signals */
#endif /* HAVE_SIGACTION && !HAVE_SIGSET */
/*
* Test the side channel descriptor before calling papOpen() since it may open
* an unused fd 4 (a.k.a. CUPS_SC_FD)...
*/
FD_ZERO(&readSet);
FD_SET(CUPS_SC_FD, &readSet);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
if ((select(CUPS_SC_FD+1, &readSet, NULL, NULL, &timeout)) >= 0)
use_sidechannel = 1;
else
use_sidechannel = 0;
/* try to find our printer */
if ((err = nbp_make_entity(&entity, name, type, zone)) != noErr)
{
fprintf(stderr, "ERROR: Unable to make AppleTalk address: %s\n", strerror(errno));
goto Exit;
}
/*
* Remember when we started looking for the printer.
*/
start_time = time(NULL);
retry.interval = 1;
retry.retries = 5;
retry.backoff = 0;
fprintf(stderr, "STATE: +connecting-to-device\n");
/* Loop forever trying to get an open session with the printer. */
for (;;)
{
/* Make sure it's okay to use appletalk */
if (okayToUseAppleTalk())
{
/* Clear this printer-state-reason in case we've set it */
fprintf(stderr, "STATE: -apple-appletalk-disabled-warning\n");
/* Resolve the name into an address. Returns the number found or an error */
if ((err = nbp_lookup(&entity, &tuple, 1, &retry)) > 0)
{
if (err > 1)
fprintf(stderr, "DEBUG: Found more than one printer with the name \"%s\"\n", name);
if (nbp_failures)
{
fprintf(stderr, "STATE: -apple-nbp-lookup-warning\n");
nbp_failures = 0;
}
/* Open a connection to the device */
if ((err = papOpen(&tuple, &gConnID, &gSockfd, &gSessionAddr, &flowQuantum)) == 0)
break;
fprintf(stderr, "WARNING: Unable to open \"%s:%s\": %s\n", name, zone, strerror(err));
}
else
{
/* It's not unusual to have to call nbp_lookup() twice before it's sucessful... */
if (++nbp_failures > 2)
{
retry.interval = 2;
retry.retries = 3;
fprintf(stderr, "STATE: +apple-nbp-lookup-warning\n");
fprintf(stderr, "WARNING: Printer not responding\n");
}
}
}
else
{
fprintf(stderr, "STATE: +apple-appletalk-disabled-warning\n");
fprintf(stderr, "INFO: AppleTalk disabled in System Preferences.\n");
}
elasped_time = time(NULL) - start_time;
if (connect_timeout == -1)
connect_timeout = connectTimeout();
if (connect_timeout && elasped_time > connect_timeout)
{
fprintf(stderr, "ERROR: Printer not responding\n");
err = ETIMEDOUT;
goto Exit; /* Waiting too long... */
}
else if (elasped_time < (30 * 60))
sleep_time = 10; /* Waiting < 30 minutes */
else if (elasped_time < (24 * 60 * 60))
sleep_time = 30; /* Waiting < 24 hours */
else
sleep_time = 60; /* Waiting > 24 hours */
fprintf(stderr, "DEBUG: sleeping %d seconds...\n", (int)sleep_time);
sleep(sleep_time);
}
fprintf(stderr, "STATE: -connecting-to-device\n");
/*
* Now that we are connected to the printer ignore SIGTERM so that we
* can finish out any page data the driver sends (e.g. to eject the
* current page... if we are printing data from a file then catch the
* signal so we can send a PAP Close packet (otherwise you can't cancel
* raw jobs...)
*/
#ifdef HAVE_SIGSET /* Use System V signals over POSIX to avoid bugs */
sigset(SIGTERM, (argc < 7) ? SIG_IGN : signalHandler);
#elif defined(HAVE_SIGACTION)
memset(&action, 0, sizeof(action));
sigemptyset(&action.sa_mask);
action.sa_handler = (argc < 7) ? SIG_IGN : signalHandler;
sigaction(SIGTERM, &action, NULL);
#else
signal(SIGTERM, (argc < 7) ? SIG_IGN : signalHandler);
#ifdef DEBUG
/* Makes debugging easier; otherwise printer will be busy for several minutes */
signal(SIGINT, signalHandler);
#endif /* DEBUG */
#endif /* HAVE_SIGSET */
fprintf(stderr, "INFO: Sending data\n");
sendDataAddr = tuple.enu_addr;
/* Start the tickle packets and set a timeout alarm */
if ((err = papSendRequest(gSockfd, &gSessionAddr, gConnID, AT_PAP_TYPE_TICKLE, 0, false, false)) < 0)
{
perror("ERROR: Unable to send PAP tickle request");
goto Exit;
}
signal(SIGALRM, signalHandler);
alarm(PAP_TIMEOUT);
/* Prime the pump with an initial send-data packet */
if ((err = papSendRequest(gSockfd, &gSessionAddr, gConnID, AT_PAP_TYPE_SEND_DATA, 0xFF, true, true)) < 0)
{
perror("ERROR: Unable to send initial PAP send data request");
goto Exit;
}
/* Set non-blocking mode on our data source descriptor */
val = fcntl(fdin, F_GETFL, 0);
fcntl(fdin, F_SETFL, val | O_NONBLOCK);
fileBufferNbytes = 0;
fileTbytes = 0;
fileEOFRead = fileEOFSent = false;
maxfdp1 = MAX(fdin, gSockfd) + 1;
if (use_sidechannel && CUPS_SC_FD >= maxfdp1)
maxfdp1 = CUPS_SC_FD + 1;
if (gStatusInterval != 0)
{
timeout.tv_usec = 0;
nextStatusTime = time(NULL) + gStatusInterval;
timeoutPtr = &timeout;
}
else
timeoutPtr = NULL;
for (;;)
{
/* Set up our descriptors for the select */
FD_ZERO(&readSet);
FD_SET(gSockfd, &readSet);
if (fileBufferNbytes == 0 && fileEOFRead == false)
FD_SET(fdin, &readSet);
if (use_sidechannel)
FD_SET(CUPS_SC_FD, &readSet);
/* Set the select timeout value based on the next status interval */
if (gStatusInterval != 0)
{
now = time(NULL);
timeout.tv_sec = (nextStatusTime > now) ? nextStatusTime - now : 1;
}
/* Wait here for something interesting to happen */
if ((err = select(maxfdp1, &readSet, 0, 0, timeoutPtr)) < 0)
{
perror("ERROR: select");
break;
}
if (err == 0 || (gStatusInterval != 0 && time(NULL) >= nextStatusTime))
{
/* Time to send a status request */
if ((err = papSendRequest(gSockfd, &tuple.enu_addr, 0, AT_PAP_TYPE_SEND_STATUS, 0x01, false, false)) < 0)
perror("WARNING: Unable to send PAP status request");
if (gStatusInterval)
nextStatusTime = time(NULL) + gStatusInterval;
}
/*
* Check if we have a side-channel request ready...
*/
if (use_sidechannel && FD_ISSET(CUPS_SC_FD, &readSet))
sidechannel_request();
/* Was there an event on the input stream? */
if (FD_ISSET(fdin, &readSet))
{
FD_CLR(fdin, &readSet);
assert(fileBufferNbytes == 0);
fileBufferNbytes = read(fdin, fileBuffer, MIN(sizeof(fileBuffer), AT_PAP_DATA_SIZE * flowQuantum));
if (fileBufferNbytes == 0)
fileEOFRead = true;
if (fileEOFSent == false && fileBufferNbytes >= 0 && gSendDataID != 0)
{
fprintf(stderr, "DEBUG: -> PAP_DATA %d bytes %s\n", fileBufferNbytes, fileEOFRead ? "with EOF" : "");
papWrite(gSockfd, &sendDataAddr, gSendDataID, gConnID, flowQuantum, fileBuffer, fileBufferNbytes, fileEOFRead);
fileTbytes += fileBufferNbytes;
if (argc > 6 && !gErrorlogged)
fprintf(stderr, "DEBUG: Sending print file, %qd bytes\n", (off_t)fileTbytes);
fileBufferNbytes = 0;
gSendDataID = 0;
if (fileEOFRead)
{
fileEOFSent = true;
if (gWaitEOF == false || fileTbytes == 0)
{
err = 0;
goto Exit;
}
}
}
}
/* Was there an event on the output stream? */
if (FD_ISSET(gSockfd, &readSet))
{
if ((rc = atp_look(gSockfd)) < 0)
{
perror("ERROR: Unable to look for PAP response");
break;
}
if (rc > 0)
{
/* It's an ATP response */
resp.resp[0].iov_base = sockBuffer;
resp.resp[0].iov_len = sizeof(sockBuffer) - 1;
resp.bitmap = 0x01;
if ((err = atp_getresp(gSockfd, &tid, &resp)) < 0)
{
perror("ERROR: Unable to get PAP response");
break;
}
userdata = resp.userdata[0];
}
else
{
/* It's an ATP request */
reqlen = sizeof(atpReqBuf);
if ((err = atp_getreq(gSockfd, &src, atpReqBuf, &reqlen, &userdata, &xo, &tid, &bitmap, 0)) < 0)
{
perror("ERROR: Unable to get PAP request");
break;
}
}
fprintf(stderr, "DEBUG: <- %s\n", packet_name(TYPE_OF(userdata)));
switch (TYPE_OF(userdata))
{
case AT_PAP_TYPE_SEND_STS_REPLY: /* Send-Status-Reply packet */
if (resp.bitmap & 1)
{
char *iov_base = (char *)resp.resp[0].iov_base;
statusUpdate(&iov_base[5], iov_base[4]);
}
break;
case AT_PAP_TYPE_SEND_DATA: /* Send-Data packet */
sendDataAddr.socket = src.socket;
gSendDataID = tid;
recvSequence = OSReadBigInt16(&SEQUENCE_NUM(userdata), 0);
if ((fileBufferNbytes > 0 || fileEOFRead) && fileEOFSent == false)
{
fprintf(stderr, "DEBUG: -> PAP_DATA %d bytes %s\n", fileBufferNbytes, fileEOFRead ? "with EOF" : "");
papWrite(gSockfd, &sendDataAddr, gSendDataID, gConnID, flowQuantum, fileBuffer, fileBufferNbytes, fileEOFRead);
fileTbytes += fileBufferNbytes;
if (argc > 6 && !gErrorlogged)
fprintf(stderr, "DEBUG: Sending print file, %qd bytes\n", (off_t)fileTbytes);
fileBufferNbytes = 0;
gSendDataID = 0;
if (fileEOFRead)
{
fileEOFSent = true;
if (gWaitEOF == false)
{
err = 0;
goto Exit;
}
}
}
break;
case AT_PAP_TYPE_DATA: /* Data packet */
for (len=0, i=0; i < ATP_TRESP_MAX; i++)
{
if (resp.bitmap & (1 << i))
len += resp.resp[i].iov_len;
}
fprintf(stderr, "DEBUG: <- PAP_DATA %d bytes %s\n", len, IS_PAP_EOF(userdata) ? "with EOF" : "");
if (len > 0)
{
char *pLineBegin, *pCommentEnd, *pChar;
char *logLevel;
char logstr[512];
int logstrlen;
cupsBackChannelWrite(sockBuffer, len, 1.0);
sockBuffer[len] = '\0'; /* We always reserve room for the nul so we can use strstr() below*/
pLineBegin = sockBuffer;
/* If there are PostScript status comments in the buffer log them.
*
* This logic shouldn't be in the backend but until we get backchannel
* data in CUPS 1.2 it has to live here.
*/
while (pLineBegin < sockBuffer + len &&
(pLineBegin = strstr(pLineBegin, "%%[")) != NULL &&
(pCommentEnd = strstr(pLineBegin, "]%%")) != NULL)
{
pCommentEnd += 3; /* Skip past "]%%" */
*pCommentEnd = '\0'; /* There's always room for the nul */
/* Strip the CRs & LFs before writing it to stderr */
for (pChar = pLineBegin; pChar < pCommentEnd; pChar++)
if (*pChar == '\r' || *pChar == '\n')
*pChar = ' ';
if (strncasecmp(pLineBegin, "%%[ Error:", 10) == 0)
{
/* logLevel should be "ERROR" here but this causes PrintCenter
* to pause the queue which in turn clears this error, which
* restarts the job. So the job ends up in an infinite loop with
* the queue being held/un-held. Just make it DEBUG for now until
* we fix notifications later.
*/
logLevel = "DEBUG";
gErrorlogged = true;
}
else if (strncasecmp(pLineBegin, "%%[ Flushing", 12) == 0)
logLevel = "DEBUG";
else
logLevel = "INFO";
if ((logstrlen = snprintf(logstr, sizeof(logstr), "%s: %s\n", logLevel, pLineBegin)) >= sizeof(logstr))
{
/* If the string was trucnated make sure it has a linefeed before the nul */
logstrlen = sizeof(logstr) - 1;
logstr[logstrlen - 1] = '\n';
}
write(fderr, logstr, logstrlen);
pLineBegin = pCommentEnd + 1;
}
}
if (IS_PAP_EOF(userdata) != 0)
{
/* If this is EOF then were we expecting it? */
if (fileEOFSent == true)
goto Exit;
else
{
fprintf(stderr, "WARNING: Printer sent unexpected EOF\n");
}
}
if ((err = papSendRequest(gSockfd, &gSessionAddr, gConnID, AT_PAP_TYPE_SEND_DATA, 0xFF, true, true)) < 0)
{
fprintf(stderr, "ERROR: Error %d sending PAPSendData resuest: %s\n", err, strerror(errno));
goto Exit;
}
break;
case AT_PAP_TYPE_TICKLE: /* Tickle packet */
break;
case AT_PAP_TYPE_CLOSE_CONN: /* Close-Connection packet */
/* We shouldn't normally see this. */
papCloseResp(gSockfd, &gSessionAddr, xo, tid, gConnID);
/* If this is EOF then were we expecting it? */
if (fileEOFSent == true)
{
fprintf(stderr, "WARNING: Printer sent unexpected EOF\n");
}
else
{
fprintf(stderr, "ERROR: Printer sent unexpected EOF\n");
}
goto Exit;
break;
case AT_PAP_TYPE_OPEN_CONN: /* Open-Connection packet */
case AT_PAP_TYPE_OPEN_CONN_REPLY: /* Open-Connection-Reply packet */
case AT_PAP_TYPE_SEND_STATUS: /* Send-Status packet */
case AT_PAP_TYPE_CLOSE_CONN_REPLY: /* Close-Connection-Reply packet */
fprintf(stderr, "WARNING: Unexpected PAP packet of type %d\n", TYPE_OF(userdata));
break;
default:
fprintf(stderr, "WARNING: Unknown PAP packet of type %d\n", TYPE_OF(userdata));
break;
}
if (CONNID_OF(userdata) == gConnID)
{
/* Reset tickle timer */
alarm(0);
alarm(PAP_TIMEOUT);
}
}
}
Exit:
/*
* Close the socket and return...
*/
papClose();
return err;
}
#pragma mark -
/*!
* @function papOpen
* @abstract Open a pap session to a printer.
*
* @param tuple nbp address of printer
* @param connID returned pap connection id
* @param fd returned socket descriptor
* @param sessionAddr returned session address
* @param flowQuantum returned flow quantum (usually 8)
*
* @result A non-zero return value for errors
*/
static int papOpen(at_nbptuple_t* tuple, u_char* connID, int* fd,
at_inet_t* sessionAddr, u_char* flowQuantum)
{
int result,
open_result,
userdata,
atp_err;
time_t tm,
waitTime;
char data[10],
rdata[ATP_DATA_SIZE];
u_char *puserdata;
at_socket socketfd;
at_resp_t resp;
at_retry_t retry;
result = 0;
socketfd = 0;
puserdata = (u_char *)&userdata;
fprintf(stderr, "INFO: Opening connection\n");
if ((*fd = atp_open(&socketfd)) < 0)
return -1;
/*
* Build the open connection request packet.
*/
tm = time(NULL);
srand(tm);
*connID = (rand()&0xff) | 0x01;
puserdata[0] = *connID;
puserdata[1] = AT_PAP_TYPE_OPEN_CONN;
puserdata[2] = 0;
puserdata[3] = 0;
retry.interval = 2;
retry.retries = 5;
resp.bitmap = 0x01;
resp.resp[0].iov_base = rdata;
resp.resp[0].iov_len = sizeof(rdata);
data[0] = socketfd;
data[1] = 8;
for (;;)
{
waitTime = time(NULL) - tm;
OSWriteBigInt16(&data[2], 0, (u_short)waitTime);
fprintf(stderr, "DEBUG: -> %s\n", packet_name(AT_PAP_TYPE_OPEN_CONN));
if ((atp_err = atp_sendreq(*fd, &tuple->enu_addr, data, 4, userdata, 1, 0,
0, &resp, &retry, 0)) < 0)
{
statusUpdate("Destination unreachable", 23);
result = EHOSTUNREACH;
break;
}
puserdata = (u_char *)&resp.userdata[0];
open_result = OSReadBigInt16(&rdata[2], 0);
fprintf(stderr, "DEBUG: <- %s, status %d\n", packet_name(puserdata[1]),
open_result);
/*
* Just for the sake of our sanity check the other fields in the packet
*/
if (puserdata[1] != AT_PAP_TYPE_OPEN_CONN_REPLY ||
(open_result == 0 && (puserdata[0] & 0xff) != *connID))
{
result = EINVAL;
break;
}
statusUpdate(&rdata[5], rdata[4] & 0xff);
/*
* if the connection established okay exit from the loop
*/
if (open_result == 0)
break;
sleep(1);
}
if (result == 0)
{
/* Update the session address
*/
sessionAddr->net = tuple->enu_addr.net;
sessionAddr->node = tuple->enu_addr.node;
sessionAddr->socket = rdata[0];
*flowQuantum = rdata[1];
}
else
{
atp_close(*fd);
*fd = 0;
sleep(1);
}
return result;
}
/*!
* @function papClose
* @abstract End a PAP session by canceling outstanding send-data & tickle
* transactions and sending a PAP close request.
*
* @result A non-zero return value for errors
*/
static int papClose()
{
int fd;
u_short tmpID;
int result;
unsigned char rdata[ATP_DATA_SIZE];
int userdata;
u_char *puserdata = (u_char *)&userdata;
at_resp_t resp;
at_retry_t retry;
if (gSockfd != 0)
{
fd = gSockfd;
gSockfd = 0;
alarm(0);
/* Cancel the pending send-data and tickle trnsactions
*/
if (gSendDataID)
{
tmpID = gSendDataID;
gSendDataID = 0;
papCancelRequest(fd, tmpID);
}
if (gTickleID)
{
tmpID = gTickleID;
gTickleID = 0;
papCancelRequest(fd, tmpID);
}
/* This is a workaround for bug #2735145. The problem is papWrite()
* returns before the ATP TRel arrives for it. If we send the pap close packet
* before this release then the printer can drop the last data packets.
* The effect on an Epson printer is the last page doesn't print, on HP it
* doesn't close the pap session.
*/
if (gWaitEOF == false)
sleep(2);
fprintf(stderr, "DEBUG: -> %s\n", packet_name(AT_PAP_TYPE_CLOSE_CONN));
puserdata[0] = gConnID;
puserdata[1] = AT_PAP_TYPE_CLOSE_CONN;
puserdata[2] = 0;
puserdata[3] = 0;
retry.interval = 2;
retry.retries = 5;
resp.bitmap = 0x01;
resp.resp[0].iov_base = rdata;
resp.resp[0].iov_len = sizeof(rdata);
result = atp_sendreq(fd, &gSessionAddr, 0, 0, userdata, 1, 0, 0, &resp, &retry, 0);
result = close(fd);
}
return noErr;
}
/*!
* @function papWrite
* @abstract Write bytes to a printer.
*
* @param sockfd socket descriptor
* @param dest destination address
* @param tid transaction id
* @param connID connection id
* @param flowQuantum returned flow quantum (usually 8)
* @param data pointer to the data
* @param len number of bytes to send
* @param eof pap eof flag
*
* @result A non-zero return value for errors
*/
static int papWrite(int sockfd, at_inet_t* dest, u_short tid, u_char connID, u_char flowQuantum, char* data, int len, int eof)
{
int result;
int i;
u_char* puserdata;
at_resp_t resp;
/* fprintf(stderr, "DEBUG: papWrite(%d%s) to %d,%d,%d; %d\n", len, eof ? " EOF":"", dest->net, dest->node, dest->socket, connID); */
if (len > AT_PAP_DATA_SIZE * flowQuantum)
{
fprintf(stderr, "DEBUG: papWrite() len of %d is too big!\n", len);
errno = E2BIG;
return -1;
}
/*
* Break up the outgoing data into a set of
* response packets to reply to an incoming
* PAP 'SENDDATA' request
*/
for (i = 0; i < flowQuantum; i++)
{
resp.userdata[i] = 0;
puserdata = (u_char *)&resp.userdata[i];
puserdata[PAP_CONNID] = connID;
puserdata[PAP_TYPE] = AT_PAP_TYPE_DATA;
puserdata[PAP_EOF] = eof ? 1 : 0;
resp.resp[i].iov_base = (caddr_t)data;
if (data)
data += AT_PAP_DATA_SIZE;
resp.resp[i].iov_len = MIN((int)len, (int)AT_PAP_DATA_SIZE);
len -= resp.resp[i].iov_len;
if (len == 0)
break;
}
resp.bitmap = (1 << (i + 1)) - 1;
/*
* Write out the data as a PAP 'DATA' response
*/
errno = 0;
if ((result = atp_sendrsp(sockfd, dest, true, tid, &resp)) < 0)
{
fprintf(stderr, "DEBUG: atp_sendrsp() returns %d, errno %d \"%s\"\n", result, errno, strerror(errno));
return -1;
}
return(0);
}
/*!
* @function papCloseResp
* @abstract Send a pap close response in the rare case we receive a close connection request.
*
* @param sockfd socket descriptor
* @param dest destination address
* @param tid transaction id
* @param connID connection id
*
* @result A non-zero return value for errors
*/
static int papCloseResp(int sockfd, at_inet_t* dest, int xo, u_short tid, u_char connID)
{
int result;
at_resp_t resp;
resp.bitmap = 1;
resp.userdata[0] = 0;
((u_char*)&resp.userdata[0])[PAP_CONNID] = connID;
((u_char*)&resp.userdata[0])[PAP_TYPE] = AT_PAP_TYPE_CLOSE_CONN_REPLY;
resp.resp[0].iov_base = NULL;
resp.resp[0].iov_len = 0;
if ((result = atp_sendrsp(sockfd, dest, xo, tid, &resp)) < 0)
{
fprintf(stderr, "DEBUG: atp_sendrsp() returns %d, errno %d \"%s\"\n", result, errno, strerror(errno));
return -1;
}
return 0;
}
/*!
* @function papSendRequest
* @abstract Send a pap close response in the rare case we receive a close connection request.
*
* @param sockfd socket descriptor
* @param dest destination address
* @param function pap function
* @param bitmap bitmap
* @param xo exactly once
* @param seqno sequence number
*
* @result A non-zero return value for errors
*/
static int papSendRequest(int sockfd, at_inet_t* dest, u_char connID, int function, u_char bitmap, int xo, int seqno)
{
u_short tid;
int err;
sigset_t sv, osv;
int userdata;
u_char *puserdata = (u_char *)&userdata;
at_retry_t retry;
at_resp_t resp;
static u_short pap_send_count = 0;
fprintf(stderr, "DEBUG: -> %s\n", packet_name(function));
puserdata[0] = connID;
puserdata[1] = function;
resp.bitmap = bitmap;
retry.interval = 10;
retry.retries = -1; /* was ATP_INFINITE_RETRIES */
if (seqno)
{
pap_send_count++;
if (pap_send_count == 0)
pap_send_count = 1;
OSWriteBigInt16(&puserdata[2], 0, pap_send_count);
}
else
OSWriteBigInt16(&puserdata[2], 0, 0);
sigemptyset(&sv);
sigaddset(&sv, SIGIO);
sigprocmask(SIG_SETMASK, &sv, &osv);
err = atp_sendreq(sockfd, dest, 0, 0, userdata, xo, 0, &tid, &resp, &retry, 1);
sigprocmask(SIG_SETMASK, &osv, NULL);
return err;
}
/*!
* @function papCancelRequest
* @abstract Cancel a pending pap request.
*
* @param sockfd socket descriptor
* @param tid transaction ID
*
* @result A non-zero return value for errors
*/
int papCancelRequest(int sockfd, u_short tid)
{
sigset_t sv, osv;
sigemptyset(&sv);
sigaddset(&sv, SIGIO);
sigprocmask(SIG_SETMASK, &sv, &osv);
if (atp_abort(sockfd, NULL, tid) < 0)
{
sigprocmask(SIG_SETMASK, &osv, NULL);
return -1;
}
sigprocmask(SIG_SETMASK, &osv, NULL);
return 0;
}
/*
* 'sidechannel_request()' - Handle side-channel requests.
*/
static void
sidechannel_request()
{
cups_sc_command_t command; /* Request command */
cups_sc_status_t status; /* Request/response status */
char data[2048]; /* Request/response data */
int datalen; /* Request/response data size */
datalen = sizeof(data);
if (cupsSideChannelRead(&command, &status, data, &datalen, 1.0))
{
fputs(_("WARNING: Failed to read side-channel request!\n"), stderr);
return;
}
switch (command)
{
case CUPS_SC_CMD_GET_BIDI: /* Is the connection bidirectional? */
data[0] = 1;
cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
break;
case CUPS_SC_CMD_GET_STATE: /* Return device state */
data[0] = CUPS_SC_STATE_ONLINE;
cupsSideChannelWrite(command, CUPS_SC_STATUS_OK, data, 1, 1.0);
break;
case CUPS_SC_CMD_DRAIN_OUTPUT: /* Drain all pending output */
case CUPS_SC_CMD_SOFT_RESET: /* Do a soft reset */
case CUPS_SC_CMD_GET_DEVICE_ID: /* Return IEEE-1284 device ID */
default:
cupsSideChannelWrite(command, CUPS_SC_STATUS_NOT_IMPLEMENTED,
NULL, 0, 1.0);
break;
}
return;
}
#pragma mark -
/*!
* @function statusUpdate
* @abstract Format and print a PAP status response to stderr.
*
* @param status The status response string
* @param statusLen The length of the status response string
*/
void statusUpdate(char* status, u_char statusLen)
{
static char status_str[255];
static u_char last_statusLen = 0xFF;
/* Only send this if the status has changed */
if (statusLen != last_statusLen || memcmp(status, status_str, statusLen) != 0)
{
if (statusLen > sizeof(status_str)-1)
statusLen = sizeof(status_str)-1;
last_statusLen = statusLen;
memcpy(status_str, status, statusLen);
status_str[(int)statusLen] = '\0';
/*
* Make sure the status string is in the form of a PostScript comment.
*/
if (statusLen > 3 && memcmp(status, "%%[", 3) == 0)
fprintf(stderr, "INFO: %s\n", status_str);
else
fprintf(stderr, "INFO: %%%%[ %s ]%%%%\n", status_str);
}
return;
}
/*!
* @function parseUri
* @abstract Parse a PAP URI into it's NBP components.
*
* @param argv0 The PAP URI to parse
* @param name NBP name
* @param zone NBP zone
* @param type NBP type
*
* @result A non-zero return value for errors
*/
static int parseUri(const char* argv0, char* name, char* type, char* zone)
{
char method[255], /* Method in URI */
hostname[1024], /* Hostname */
username[255], /* Username info (not used) */
resource[1024], /* Resource info (device and options) */
*resourcePtr,
*typePtr,
*options, /* Pointer to options */
*optionName, /* Name of option */
*value, /* Value of option */
sep; /* Separator character */
int port; /* Port number (not used) */
int statusInterval; /* */
/*
* Extract the device name and options from the URI...
*/
method[0] = username[0] = hostname[0] = resource[0] = '\0';
port = 0;
httpSeparateURI(HTTP_URI_CODING_NONE, argv0, method, sizeof(method),
username, sizeof(username),
hostname, sizeof(hostname), &port,
resource, sizeof(resource));
/*
* See if there are any options...
*/
if ((options = strchr(resource, '?')) != NULL)
{
/*
* Yup, terminate the device name string and move to the first
* character of the options...
*/
*options++ = '\0';
while (*options != '\0')
{
/*
* Get the name...
*/
optionName = options;
while (*options && *options != '=' && *options != '+' && *options != '&')
options ++;
if ((sep = *options) != '\0')
*options++ = '\0';
if (sep == '=')
{
/*
* Get the value...
*/
value = options;
while (*options && *options != '+' && *options != '&')
options ++;
if (*options)
*options++ = '\0';
}
else
value = (char *)"";
/*
* Process the option...
*/
if (!strcasecmp(optionName, "waiteof"))
{
/*
* Wait for the end of the print file?
*/
if (!strcasecmp(value, "on") ||
!strcasecmp(value, "yes") ||
!strcasecmp(value, "true"))
{
gWaitEOF = true;
}
else if (!strcasecmp(value, "off") ||
!strcasecmp(value, "no") ||
!strcasecmp(value, "false"))
{
gWaitEOF = false;
}
else
{
fprintf(stderr, "WARNING: Boolean expected for waiteof option \"%s\"\n", value);
}
}
else if (!strcasecmp(optionName, "status"))
{
/*
* Set status reporting interval...
*/
statusInterval = atoi(value);
if (value[0] < '0' || value[0] > '9' || statusInterval < 0)
{
fprintf(stderr, "WARNING: number expected for status option \"%s\"\n",
value);
}
else
{
gStatusInterval = statusInterval;
}
}
}
}
resourcePtr = resource;
if (*resourcePtr == '/')
resourcePtr++;
/* If the resource has a slash we assume the slash seperates the AppleTalk object
* name from the AppleTalk type. If the slash is not present we assume the AppleTalk
* type is LaserWriter.
*/
typePtr = strchr(resourcePtr, '/');
if (typePtr != NULL)
{
*typePtr++ = '\0';
}
else
{
typePtr = "LaserWriter";
}
removePercentEscapes(hostname, zone, NBP_NVE_STR_SIZE + 1);
removePercentEscapes(resourcePtr, name, NBP_NVE_STR_SIZE + 1);
removePercentEscapes(typePtr, type, NBP_NVE_STR_SIZE + 1);
return 0;
}
/*!
* @function addPercentEscapes
* @abstract Encode a string with percent escapes
*
* @param src The source C string
* @param dst Desination buffer
* @param dstMax Size of desination buffer
*
* @result A non-zero return value for errors
*/
static int addPercentEscapes(const char* src, char* dst, int dstMax)
{
char c;
char *dstEnd = dst + dstMax - 1; /* -1 to leave room for the NUL */
while (*src)
{
c = *src++;
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || (c == '.' || c == '-' || c == '*' || c == '_'))
{
if (dst >= dstEnd)
return -1;
*dst++ = c;
}
else
{
if (dst >= dstEnd - 2)
return -1;
snprintf(dst, dstEnd - dst, "%%%02x", c);
dst += 3;
}
}
*dst = '\0';
return 0;
}
/*!
* @function removePercentEscapes
* @abstract Returns a string with any percent escape sequences replaced with their equivalent character
*
* @param src Source buffer
* @param srclen Number of bytes in source buffer
* @param dst Desination buffer
* @param dstMax Size of desination buffer
*
* @result A non-zero return value for errors
*/
static int removePercentEscapes(const char* src, char* dst, int dstMax)
{
int c;
const char *dstEnd = dst + dstMax;
while (*src && dst < dstEnd)
{
c = *src++;
if (c == '%')
{
sscanf(src, "%02x", &c);
src += 2;
}
*dst++ = (char)c;
}
if (dst >= dstEnd)
return -1;
*dst = '\0';
return 0;
}
/*!
* @function nbptuple_compare
* @abstract An NBP comparator for qsort.
*
* @result p1<p2: -1, p1=p2: 0, p1>p2: 1
*/
int nbptuple_compare(const void *p1, const void *p2)
{
int result;
int len = MIN(((at_nbptuple_t*)p1)->enu_entity.object.len,
((at_nbptuple_t*)p2)->enu_entity.object.len);
if ((result = memcmp(((at_nbptuple_t*)p1)->enu_entity.object.str, ((at_nbptuple_t*)p2)->enu_entity.object.str, len)) == 0)
{
if (((at_nbptuple_t*)p1)->enu_entity.object.len < ((at_nbptuple_t*)p2)->enu_entity.object.len)
result = -1;
else if (((at_nbptuple_t*)p1)->enu_entity.object.len > ((at_nbptuple_t*)p2)->enu_entity.object.len)
result = 1;
else
result = 0;
}
return result;
}
/*!
* @function okayToUseAppleTalk
* @abstract Returns true if AppleTalk is available and enabled.
*
* @result non-zero if AppleTalk is enabled
*/
static int okayToUseAppleTalk()
{
int atStatus = checkATStack();
/* I think the test should be:
* return atStatus == RUNNING || atStatus == LOADED;
* but when I disable AppleTalk from the network control panel and
* reboot, AppleTalk shows up as loaded. The test empirically becomes
* the following:
*/
return atStatus == RUNNING;
}
/*!
* @function packet_name
* @abstract Returns packet name string.
*
* @result A string
*/
static const char *packet_name(u_char x)
{
switch (x)
{
case AT_PAP_TYPE_OPEN_CONN: return "PAP_OPEN_CONN";
case AT_PAP_TYPE_OPEN_CONN_REPLY: return "PAP_OPEN_CONN_REPLY";
case AT_PAP_TYPE_SEND_DATA: return "PAP_SEND_DATA";
case AT_PAP_TYPE_DATA: return "PAP_DATA";
case AT_PAP_TYPE_TICKLE: return "PAP_TICKLE";
case AT_PAP_TYPE_CLOSE_CONN: return "PAP_CLOSE_CONN";
case AT_PAP_TYPE_CLOSE_CONN_REPLY: return "PAP_CLOSE_CONN_REPLY";
case AT_PAP_TYPE_SEND_STATUS: return "PAP_SEND_STATUS";
case AT_PAP_TYPE_SEND_STS_REPLY: return "PAP_SEND_STS_REPLY";
case AT_PAP_TYPE_READ_LW: return "PAP_READ_LW";
}
return "<Unknown>";
}
/*!
* @function connectTimeout
* @abstract Returns the connect timeout preference value.
*/
static int connectTimeout()
{
CFPropertyListRef value;
SInt32 connect_timeout = (7 * 24 * 60 * 60); /* Default timeout is one week... */
value = CFPreferencesCopyValue(CFSTR("timeout"), CFSTR("com.apple.print.backends"),
kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
if (value != NULL)
{
if (CFGetTypeID(value) == CFNumberGetTypeID())
CFNumberGetValue(value, kCFNumberSInt32Type, &connect_timeout);
CFRelease(value);
}
return connect_timeout;
}
/*!
* @function signalHandler
* @abstract A signal handler so we can clean up the pap session before exiting.
*
* @param sigraised The signal raised
*
* @result Never returns
*/
static void signalHandler(int sigraised)
{
fprintf(stderr, "ERROR: There was a timeout error while sending data to the printer\n");
papClose();
_exit(1);
}