#! /usr/bin/env python # -*- coding: ISO-8859-15 -*- # Tea4CUPS : Tee for CUPS # # (c) 2005 Jerome Alet # (c) 2005 Peter Stuge # 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; either version 2 of the License, or # (at your option) any later version. # # 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, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # $Id$ # # import sys import os import pwd import popen2 import errno import md5 import cStringIO import shlex import tempfile import ConfigParser import select import signal import time from struct import unpack version = "3.00_unofficial" class TeeError(Exception): """Base exception for Tea4CUPS related stuff.""" def __init__(self, message = ""): self.message = message Exception.__init__(self, message) def __repr__(self): return self.message __str__ = __repr__ class ConfigError(TeeError) : """Configuration related exceptions.""" pass class IPPError(TeeError) : """IPP related exceptions.""" pass class IPPRequest : """A class for IPP requests. Usage : fp = open("/var/spool/cups/c00001", "rb") message = IPPRequest(fp.read()) fp.close() message.parse() # print message.dump() # dumps an equivalent to the original IPP message # print str(message) # returns a string of text with the same content as below print "IPP version : %s.%s" % message.version print "IPP operation Id : 0x%04x" % message.operation_id print "IPP request Id : 0x%08x" % message.request_id for attrtype in message.attributes_types : attrdict = getattr(message, "%s_attributes" % attrtype) if attrdict : print "%s attributes :" % attrtype.title() for key in attrdict.keys() : print " %s : %s" % (key, attrdict[key]) if message.data : print "IPP datas : ", repr(message.data) """ attributes_types = ("operation", "job", "printer", "unsupported", \ "subscription", "event_notification") def __init__(self, data="", version=None, operation_id=None, \ request_id=None, debug=0) : """Initializes an IPP Message object. Parameters : data : the complete IPP Message's content. debug : a boolean value to output debug info on stderr. """ self.debug = debug self._data = data self.parsed = 0 # Initializes message if version is not None : try : self.version = [int(p) for p in version.split(".")] except AttributeError : if len(version) == 2 : # 2-tuple self.version = version else : try : self.version = [int(p) for p in str(float(version)).split(".")] except : self.version = (1, 1) # default version number self.operation_id = operation_id self.request_id = request_id self.data = "" # Initialize attributes mappings for attrtype in self.attributes_types : setattr(self, "%s_attributes" % attrtype, {}) # Initialize tags self.tags = [ None ] * 256 # by default all tags reserved # Delimiter tags self.tags[0x01] = "operation-attributes-tag" self.tags[0x02] = "job-attributes-tag" self.tags[0x03] = "end-of-attributes-tag" self.tags[0x04] = "printer-attributes-tag" self.tags[0x05] = "unsupported-attributes-tag" self.tags[0x06] = "subscription-attributes-tag" self.tags[0x07] = "event-notification-attributes-tag" # out of band values self.tags[0x10] = "unsupported" self.tags[0x11] = "reserved-for-future-default" self.tags[0x12] = "unknown" self.tags[0x13] = "no-value" self.tags[0x15] = "not-settable" self.tags[0x16] = "delete-attribute" self.tags[0x17] = "admin-define" # integer values self.tags[0x20] = "generic-integer" self.tags[0x21] = "integer" self.tags[0x22] = "boolean" self.tags[0x23] = "enum" # octetString self.tags[0x30] = "octetString-with-an-unspecified-format" self.tags[0x31] = "dateTime" self.tags[0x32] = "resolution" self.tags[0x33] = "rangeOfInteger" self.tags[0x34] = "begCollection" # TODO : find sample files for testing self.tags[0x35] = "textWithLanguage" self.tags[0x36] = "nameWithLanguage" self.tags[0x37] = "endCollection" # character strings self.tags[0x40] = "generic-character-string" self.tags[0x41] = "textWithoutLanguage" self.tags[0x42] = "nameWithoutLanguage" self.tags[0x44] = "keyword" self.tags[0x45] = "uri" self.tags[0x46] = "uriScheme" self.tags[0x47] = "charset" self.tags[0x48] = "naturalLanguage" self.tags[0x49] = "mimeMediaType" self.tags[0x4a] = "memberAttrName" # Reverse mapping to generate IPP messages self.dictags = {} for i in range(len(self.tags)) : value = self.tags[i] if value is not None : self.dictags[value] = i def printInfo(self, msg) : """Prints a debug message.""" if self.debug : sys.stderr.write("%s\n" % msg) sys.stderr.flush() def __str__(self) : """Returns the parsed IPP message in a readable form.""" if not self.parsed : return "" else : buffer = [] buffer.append("IPP version : %s.%s" % self.version) buffer.append("IPP operation Id : 0x%04x" % self.operation_id) buffer.append("IPP request Id : 0x%08x" % self.request_id) for attrtype in self.attributes_types : attrdict = getattr(self, "%s_attributes" % attrtype) if attrdict : buffer.append("%s attributes :" % attrtype.title()) for key in attrdict.keys() : buffer.append(" %s : %s" % (key, attrdict[key])) if self.data : buffer.append("IPP datas : %s" % repr(message.data)) return "\n".join(buffer) def dump(self) : """Generates an IPP Message. Returns the message as a string of text. """ buffer = [] if None not in (self.version, self.operation_id, self.request_id) : buffer.append(chr(self.version[0]) + chr(self.version[1])) buffer.append(pack(">H", self.operation_id)) buffer.append(pack(">I", self.request_id)) for attrtype in self.attributes_types : tagprinted = 0 for (attrname, value) in getattr(self, "%s_attributes" % attrtype).items() : if not tagprinted : buffer.append(chr(self.dictags["%s-attributes-tag" % attrtype])) tagprinted = 1 if type(value) != type([]) : value = [ value ] for (vtype, val) in value : buffer.append(chr(self.dictags[vtype])) buffer.append(pack(">H", len(attrname))) buffer.append(attrname) if vtype in ("integer", "enum") : buffer.append(pack(">H", 4)) buffer.append(pack(">I", val)) elif vtype == "boolean" : buffer.append(pack(">H", 1)) buffer.append(chr(val)) else : buffer.append(pack(">H", len(val))) buffer.append(val) buffer.append(chr(self.dictags["end-of-attributes-tag"])) buffer.append(self.data) return "".join(buffer) def parse(self) : """Parses an IPP Request. NB : Only a subset of RFC2910 is implemented. """ self._curname = None self._curdict = None self.version = (ord(self._data[0]), ord(self._data[1])) self.operation_id = unpack(">H", self._data[2:4])[0] self.request_id = unpack(">I", self._data[4:8])[0] self.position = 8 endofattributes = self.dictags["end-of-attributes-tag"] maxdelimiter = self.dictags["event-notification-attributes-tag"] try : tag = ord(self._data[self.position]) while tag != endofattributes : self.position += 1 name = self.tags[tag] if name is not None : func = getattr(self, name.replace("-", "_"), None) if func is not None : self.position += func() if ord(self._data[self.position]) > maxdelimiter : self.position -= 1 continue tag = ord(self._data[self.position]) except IndexError : raise IPPError, "Unexpected end of IPP message." # Now transform all one-element lists into single values for attrtype in self.attributes_types : attrdict = getattr(self, "%s_attributes" % attrtype) for (key, value) in attrdict.items() : if len(value) == 1 : attrdict[key] = value[0] self.data = self._data[self.position+1:] self.parsed = 1 def parseTag(self) : """Extracts information from an IPP tag.""" pos = self.position tagtype = self.tags[ord(self._data[pos])] pos += 1 posend = pos2 = pos + 2 namelength = unpack(">H", self._data[pos:pos2])[0] if not namelength : name = self._curname else : posend += namelength self._curname = name = self._data[pos2:posend] pos2 = posend + 2 valuelength = unpack(">H", self._data[posend:pos2])[0] posend = pos2 + valuelength value = self._data[pos2:posend] if tagtype in ("integer", "enum") : value = unpack(">I", value)[0] elif tagtype == "boolean" : value = ord(value) oldval = self._curdict.setdefault(name, []) oldval.append((tagtype, value)) self.printInfo("%s(%s) : %s" % (name, tagtype, value)) return posend - self.position def operation_attributes_tag(self) : """Indicates that the parser enters into an operation-attributes-tag group.""" self.printInfo("Start of operation_attributes_tag") self._curdict = self.operation_attributes return self.parseTag() def job_attributes_tag(self) : """Indicates that the parser enters into a job-attributes-tag group.""" self.printInfo("Start of job_attributes_tag") self._curdict = self.job_attributes return self.parseTag() def printer_attributes_tag(self) : """Indicates that the parser enters into a printer-attributes-tag group.""" self.printInfo("Start of printer_attributes_tag") self._curdict = self.printer_attributes return self.parseTag() def unsupported_attributes_tag(self) : """Indicates that the parser enters into an unsupported-attributes-tag group.""" self.printInfo("Start of unsupported_attributes_tag") self._curdict = self.unsupported_attributes return self.parseTag() def subscription_attributes_tag(self) : """Indicates that the parser enters into a subscription-attributes-tag group.""" self.printInfo("Start of subscription_attributes_tag") self._curdict = self.subscription_attributes return self.parseTag() def event_notification_attributes_tag(self) : """Indicates that the parser enters into an event-notification-attributes-tag group.""" self.printInfo("Start of event_notification_attributes_tag") self._curdict = self.event_notification_attributes return self.parseTag() class FakeConfig : """Fakes a configuration file parser.""" def get(self, section, option, raw=0) : """Fakes the retrieval of an option.""" raise ConfigError, "Invalid configuration file : no option %s in section [%s]" % (option, section) class CupsBackend : """Base class for tools with no database access.""" def __init__(self) : """Initializes the CUPS backend wrapper.""" signal.signal(signal.SIGTERM, signal.SIG_IGN) signal.signal(signal.SIGPIPE, signal.SIG_IGN) self.MyName = "Tea4CUPS" self.myname = "tea4cups" self.pid = os.getpid() def readConfig(self) : """Reads the configuration file.""" confdir = os.environ.get("CUPS_SERVERROOT", ".") self.conffile = os.path.join(confdir, "%s.conf" % self.myname) if os.path.isfile(self.conffile) : self.config = ConfigParser.ConfigParser() self.config.read([self.conffile]) self.debug = self.isTrue(self.getGlobalOption("debug", ignore=1)) else : self.config = FakeConfig() self.debug = 1 # no config, so force debug mode ! def logInfo(self, message, level="info") : """Logs a message to CUPS' error_log file.""" try : sys.stderr.write("%s: %s v%s (PID %i) : %s\n" % (level.upper(), self.MyName, version, os.getpid(), message)) sys.stderr.flush() except IOError : pass def logDebug(self, message) : """Logs something to debug output if debug is enabled.""" if self.debug : self.logInfo(message, level="debug") def isTrue(self, option) : """Returns 1 if option is set to true, else 0.""" if (option is not None) and (option.upper().strip() in ['Y', 'YES', '1', 'ON', 'T', 'TRUE']) : return 1 else : return 0 def getGlobalOption(self, option, ignore=0) : """Returns an option from the global section, or raises a ConfigError if ignore is not set, else returns None.""" try : return self.config.get("global", option, raw=1) except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) : if not ignore : raise ConfigError, "Option %s not found in section global of %s" % (option, self.conffile) def getPrintQueueOption(self, printqueuename, option, ignore=0) : """Returns an option from the printer section, or the global section, or raises a ConfigError.""" globaloption = self.getGlobalOption(option, ignore=1) try : return self.config.get(printqueuename, option, raw=1) except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) : if globaloption is not None : return globaloption elif not ignore : raise ConfigError, "Option %s not found in section [%s] of %s" % (option, printqueuename, self.conffile) def enumBranches(self, printqueuename, branchtype="tee") : """Returns the list of branchtypes branches for a particular section's.""" branchbasename = "%s_" % branchtype.lower() try : globalbranches = [ (k, self.config.get("global", k)) for k in self.config.options("global") if k.startswith(branchbasename) ] except ConfigParser.NoSectionError, msg : raise ConfigError, "Invalid configuration file : %s" % msg try : sectionbranches = [ (k, self.config.get(printqueuename, k)) for k in self.config.options(printqueuename) if k.startswith(branchbasename) ] except ConfigParser.NoSectionError, msg : self.logInfo("No section for print queue %s : %s" % (printqueuename, msg)) sectionbranches = [] branches = {} for (k, v) in globalbranches : value = v.strip() if value : branches[k] = value for (k, v) in sectionbranches : value = v.strip() if value : branches[k] = value # overwrite any global option or set a new value else : del branches[k] # empty value disables a global option return branches def discoverOtherBackends(self) : """Discovers the other CUPS backends. Executes each existing backend in turn in device enumeration mode. Returns the list of available backends. """ # Unfortunately this method can't output any debug information # to stdout or stderr, else CUPS considers that the device is # not available. available = [] (directory, myname) = os.path.split(sys.argv[0]) if not directory : directory = "./" tmpdir = tempfile.gettempdir() lockfilename = os.path.join(tmpdir, "%s..LCK" % myname) if os.path.exists(lockfilename) : lockfile = open(lockfilename, "r") pid = int(lockfile.read()) lockfile.close() try : # see if the pid contained in the lock file is still running os.kill(pid, 0) except OSError, e : if e.errno != errno.EPERM : # process doesn't exist anymore os.remove(lockfilename) if not os.path.exists(lockfilename) : lockfile = open(lockfilename, "w") lockfile.write("%i" % self.pid) lockfile.close() allbackends = [ os.path.join(directory, b) \ for b in os.listdir(directory) if os.access(os.path.join(directory, b), os.X_OK) \ and (b != myname)] for backend in allbackends : answer = os.popen(backend, "r") try : devices = [line.strip() for line in answer.readlines()] except : devices = [] status = answer.close() if status is None : for d in devices : # each line is of the form : # 'xxxx xxxx "xxxx xxx" "xxxx xxx"' # so we have to decompose it carefully fdevice = cStringIO.StringIO(d) tokenizer = shlex.shlex(fdevice) tokenizer.wordchars = tokenizer.wordchars + \ r".:,?!~/\_$*-+={}[]()#" arguments = [] while 1 : token = tokenizer.get_token() if token : arguments.append(token) else : break fdevice.close() try : (devicetype, device, name, fullname) = arguments except ValueError : pass # ignore this 'bizarre' device else : if name.startswith('"') and name.endswith('"') : name = name[1:-1] if fullname.startswith('"') and fullname.endswith('"') : fullname = fullname[1:-1] available.append('%s %s:%s "%s+%s" "%s managed %s"' \ % (devicetype, self.myname, device, self.MyName, name, self.MyName, fullname)) os.remove(lockfilename) available.append('direct %s:// "%s+Nothing" "%s managed Virtual Printer"' \ % (self.myname, self.MyName, self.MyName)) return available def initBackend(self) : """Initializes the backend's attributes.""" # check that the DEVICE_URI environment variable's value is # prefixed with self.myname otherwise don't touch it. # If this is the case, we have to remove the prefix from # the environment before launching the real backend muststartwith = "%s:" % self.myname device_uri = os.environ.get("DEVICE_URI", "") if device_uri.startswith(muststartwith) : fulldevice_uri = device_uri[:] device_uri = fulldevice_uri[len(muststartwith):] for i in range(2) : if device_uri.startswith("/") : device_uri = device_uri[1:] try : (backend, destination) = device_uri.split(":", 1) except ValueError : if not device_uri : self.logDebug("Not attached to an existing print queue.") backend = "" else : raise TeeError, "Invalid DEVICE_URI : %s\n" % device_uri self.JobId = sys.argv[1].strip() self.UserName = sys.argv[2].strip() or pwd.getpwuid(os.geteuid())[0] # use CUPS' user when printing test pages from CUPS' web interface self.Title = sys.argv[3].strip() self.Copies = int(sys.argv[4].strip()) self.Options = sys.argv[5].strip() if len(sys.argv) == 7 : self.InputFile = sys.argv[6] # read job's datas from file else : self.InputFile = None # read job's datas from stdin self.RealBackend = backend self.DeviceURI = device_uri self.PrinterName = os.environ.get("PRINTER", "") self.Directory = self.getPrintQueueOption(self.PrinterName, "directory") self.DataFile = os.path.join(self.Directory, "%s-%s-%s-%s" % (self.myname, self.PrinterName, self.UserName, self.JobId)) (ippfilename, ippmessage) = self.parseIPPRequestFile() self.ControlFile = ippfilename (chtype, self.ClientHost) = ippmessage.operation_attributes.get("job-originating-host-name", \ ippmessage.job_attributes.get("job-originating-host-name", (None, None))) (jbtype, self.JobBilling) = ippmessage.job_attributes.get("job-billing", (None, None)) def getCupsConfigDirectives(self, directives=[]) : """Retrieves some CUPS directives from its configuration file. Returns a mapping with lowercased directives as keys and their setting as values. """ dirvalues = {} cupsroot = os.environ.get("CUPS_SERVERROOT", "/etc/cups") cupsdconf = os.path.join(cupsroot, "cupsd.conf") try : conffile = open(cupsdconf, "r") except IOError : raise TeeError, "Unable to open %s" % cupsdconf else : for line in conffile.readlines() : linecopy = line.strip().lower() for di in [d.lower() for d in directives] : if linecopy.startswith("%s " % di) : try : val = line.split()[1] except : pass # ignore errors, we take the last value in any case. else : dirvalues[di] = val conffile.close() return dirvalues def parseIPPRequestFile(self) : """Parses the IPP message file and returns a tuple (filename, parsedvalue).""" cupsdconf = self.getCupsConfigDirectives(["RequestRoot"]) requestroot = cupsdconf.get("requestroot", "/var/spool/cups") if (len(self.JobId) < 5) and self.JobId.isdigit() : ippmessagefile = "c%05i" % int(self.JobId) else : ippmessagefile = "c%s" % self.JobId ippmessagefile = os.path.join(requestroot, ippmessagefile) ippmessage = {} try : ippdatafile = open(ippmessagefile) except : self.logInfo("Unable to open IPP message file %s" % ippmessagefile, "warn") else : self.logDebug("Parsing of IPP message file %s begins." % ippmessagefile) try : ippmessage = IPPRequest(ippdatafile.read()) ippmessage.parse() except IPPError, msg : self.logInfo("Error while parsing %s : %s" % (ippmessagefile, msg), "warn") else : self.logDebug("Parsing of IPP message file %s ends." % ippmessagefile) ippdatafile.close() return (ippmessagefile, ippmessage) def exportAttributes(self) : """Exports our backend's attributes to the environment.""" os.environ["DEVICE_URI"] = self.DeviceURI # WARNING ! os.environ["TEAPRINTERNAME"] = self.PrinterName os.environ["TEADIRECTORY"] = self.Directory os.environ["TEADATAFILE"] = self.DataFile os.environ["TEAJOBSIZE"] = str(self.JobSize) os.environ["TEAMD5SUM"] = self.JobMD5Sum os.environ["TEACLIENTHOST"] = self.ClientHost or "" os.environ["TEAJOBID"] = self.JobId os.environ["TEAUSERNAME"] = self.UserName os.environ["TEATITLE"] = self.Title os.environ["TEACOPIES"] = str(self.Copies) os.environ["TEAOPTIONS"] = self.Options os.environ["TEAINPUTFILE"] = self.InputFile or "" os.environ["TEABILLING"] = self.JobBilling or "" os.environ["TEACONTROLFILE"] = self.ControlFile def saveDatasAndCheckSum(self) : """Saves the input datas into a static file.""" self.logDebug("Duplicating data stream into %s" % self.DataFile) mustclose = 0 if self.InputFile is not None : infile = open(self.InputFile, "rb") mustclose = 1 else : infile = sys.stdin CHUNK = 64*1024 # read 64 Kb at a time dummy = 0 sizeread = 0 checksum = md5.new() outfile = open(self.DataFile, "wb") while 1 : data = infile.read(CHUNK) if not data : break sizeread += len(data) outfile.write(data) checksum.update(data) if not (dummy % 32) : # Only display every 2 Mb self.logDebug("%s bytes saved..." % sizeread) dummy += 1 outfile.close() if mustclose : infile.close() self.JobSize = sizeread self.JobMD5Sum = checksum.hexdigest() self.logDebug("Job %s is %s bytes long." % (self.JobId, self.JobSize)) self.logDebug("Job %s MD5 sum is %s" % (self.JobId, self.JobMD5Sum)) def cleanUp(self) : """Cleans up the place.""" if not self.isTrue(self.getPrintQueueOption(self.PrinterName, "keepfiles", ignore=1)) : os.remove(self.DataFile) def sigtermHandler(self, signum, frame) : """Sets an attribute whenever SIGTERM is received.""" self.gotSigTerm = 1 self.logInfo("SIGTERM received for Job %s." % self.JobId) def runBranches(self) : """Launches each hook defined for the current print queue.""" self.isCancelled = 0 # did a prehook cancel the print job ? self.gotSigTerm = 0 signal.signal(signal.SIGTERM, self.sigtermHandler) serialize = self.isTrue(self.getPrintQueueOption(self.PrinterName, "serialize", ignore=1)) self.pipes = { 0: (0, 1) } branches = self.enumBranches(self.PrinterName, "prehook") for b in branches : self.pipes[b.split("_", 1)[1]] = os.pipe() retcode = self.runCommands("prehook", branches, serialize) for p in [ (k, v) for (k, v) in self.pipes.items() if k != 0 ] : os.close(p[1][1]) if not self.isCancelled and not self.gotSigTerm : if self.RealBackend : retcode = self.runOriginalBackend() if not self.gotSigTerm : os.environ["TEASTATUS"] = str(retcode) branches = self.enumBranches(self.PrinterName, "posthook") if self.runCommands("posthook", branches, serialize) : self.logInfo("An error occured during the execution of posthooks.", "warn") for p in [ (k, v) for (k, v) in self.pipes.items() if k != 0 ] : os.close(p[1][0]) signal.signal(signal.SIGTERM, signal.SIG_IGN) if not retcode : self.logInfo("OK") else : self.logInfo("An error occured, please check CUPS' error_log file.") return retcode def stdioRedirSystem(self, cmd, stdin=0, stdout=1) : """Launches a command with stdio redirected.""" # Code contributed by Peter Stuge on May 23rd and June 7th 2005 pid = os.fork() if pid == 0 : if stdin != 0 : os.dup2(stdin, 0) os.close(stdin) if stdout != 1 : os.dup2(stdout, 1) os.close(stdout) try : os.execl("/bin/sh", "sh", "-c", cmd) except OSError, msg : self.logDebug("execl() failed: %s" % msg) os._exit(-1) status = os.waitpid(pid, 0)[1] if os.WIFEXITED(status) : return os.WEXITSTATUS(status) return -1 def runCommand(self, branch, command) : """Runs a particular branch command.""" # Code contributed by Peter Stuge on June 7th 2005 self.logDebug("Launching %s : %s" % (branch, command)) btype, bname = branch.split("_", 1) if bname not in self.pipes.keys() : bname = 0 if btype == "prehook" : return self.stdioRedirSystem(command, 0, self.pipes[bname][1]) else : return self.stdioRedirSystem(command, self.pipes[bname][0]) def runCommands(self, btype, branches, serialize) : """Runs the commands for a particular branch type.""" exitcode = 0 btype = btype.lower() btypetitle = btype.title() branchlist = branches.keys() branchlist.sort() if serialize : self.logDebug("Begin serialized %ss" % btypetitle) for branch in branchlist : if self.gotSigTerm : break retcode = self.runCommand(branch, branches[branch]) self.logDebug("Exit code for %s %s on printer %s is %s" % (btype, branch, self.PrinterName, retcode)) if retcode : if (btype == "prehook") and (retcode == 255) : # -1 self.logInfo("Job %s cancelled by prehook %s" % (self.JobId, branch)) self.isCancelled = 1 else : self.logInfo("%s %s on printer %s didn't exit successfully." % (btypetitle, branch, self.PrinterName), "error") exitcode = 1 self.logDebug("End serialized %ss" % btypetitle) else : self.logDebug("Begin forked %ss" % btypetitle) pids = {} for branch in branchlist : if self.gotSigTerm : break pid = os.fork() if pid : pids[branch] = pid else : os._exit(self.runCommand(branch, branches[branch])) for (branch, pid) in pids.items() : retcode = os.waitpid(pid, 0)[1] if os.WIFEXITED(retcode) : retcode = os.WEXITSTATUS(retcode) else : retcode = -1 self.logDebug("Exit code for %s %s (PID %s) on printer %s is %s" % (btype, branch, pid, self.PrinterName, retcode)) if retcode : if (btype == "prehook") and (retcode == 255) : # -1 self.logInfo("Job %s cancelled by prehook %s" % (self.JobId, branch)) self.isCancelled = 1 else : self.logInfo("%s %s (PID %s) on printer %s didn't exit successfully." % (btypetitle, branch, pid, self.PrinterName), "error") exitcode = 1 self.logDebug("End forked %ss" % btypetitle) return exitcode def runOriginalBackend(self) : """Launches the original backend.""" originalbackend = os.path.join(os.path.split(sys.argv[0])[0], self.RealBackend) arguments = [os.environ["DEVICE_URI"]] + sys.argv[1:] self.logDebug("Starting original backend %s with args %s" % (originalbackend, " ".join(['"%s"' % a for a in arguments]))) pid = os.fork() if pid == 0 : if self.InputFile is None : f = open(self.dataFile, "rb") os.dup2(f.fileno(), 0) f.close() try : os.execve(originalbackend, arguments, os.environ) except OSError, msg : self.logDebug("execve() failed: %s" % msg) os._exit(-1) killed = 0 status = -1 while status == -1 : try : status = os.waitpid(pid, 0)[1] except OSError, (err, msg) : if (err == 4) and self.gotSigTerm : os.kill(pid, signal.SIGTERM) killed = 1 if os.WIFEXITED(status) : status = os.WEXITSTATUS(status) if status : self.logInfo("CUPS backend %s returned %d." % (originalbackend,\ status), "error") return status elif not killed : self.logInfo("CUPS backend %s died abnormally." % originalbackend,\ "error") return -1 else : return 1 if __name__ == "__main__" : # This is a CUPS backend, we should act and die like a CUPS backend wrapper = CupsBackend() if len(sys.argv) == 1 : print "\n".join(wrapper.discoverOtherBackends()) sys.exit(0) elif len(sys.argv) not in (6, 7) : sys.stderr.write("ERROR: %s job-id user title copies options [file]\n"\ % sys.argv[0]) sys.exit(1) else : try : wrapper.readConfig() wrapper.initBackend() wrapper.saveDatasAndCheckSum() wrapper.exportAttributes() retcode = wrapper.runBranches() wrapper.cleanUp() except SystemExit, e : retcode = e.code except : import traceback lines = [] for line in traceback.format_exception(*sys.exc_info()) : lines.extend([l for l in line.split("\n") if l]) msg = "ERROR: ".join(["%s (PID %s) : %s\n" % (wrapper.MyName, \ wrapper.pid, l) \ for l in (["ERROR: Tea4CUPS v%s" % version] + lines)]) sys.stderr.write(msg) sys.stderr.flush() retcode = 1 sys.exit(retcode)