Changeset 641
- Timestamp:
- 06/11/05 19:05:00 (19 years ago)
- Files:
-
- 1 modified
Legend:
- Unmodified
- Added
- Removed
-
tea4cups/trunk/tea4cups
r640 r641 15 15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 16 # GNU General Public License for more details. 17 # 17 # 18 18 # You should have received a copy of the GNU General Public License 19 19 # along with this program; if not, write to the Free Software … … 49 49 return self.message 50 50 __str__ = __repr__ 51 52 class ConfigError(TeeError) : 51 52 class ConfigError(TeeError) : 53 53 """Configuration related exceptions.""" 54 pass 55 56 class IPPError(TeeError) : 54 pass 55 56 class IPPError(TeeError) : 57 57 """IPP related exceptions.""" 58 pass 59 60 # Some IPP constants 58 pass 59 60 # Some IPP constants 61 61 OPERATION_ATTRIBUTES_TAG = 0x01 62 62 JOB_ATTRIBUTES_TAG = 0x02 … … 67 67 class IPPMessage : 68 68 """A class for IPP message files. 69 69 70 70 Usage : 71 71 72 72 fp = open("/var/spool/cups/c00001", "rb") 73 73 message = IPPMessage(fp.read()) … … 86 86 def __init__(self, data, debug=0) : 87 87 """Initializes and parses IPP Message object. 88 88 89 89 Parameters : 90 90 91 91 data : the IPP Message's content. 92 92 debug : a boolean value to output debug info on stderr. … … 97 97 setattr(self, "%s_attributes" % attrtype, {}) 98 98 self.tags = [ None ] * 256 # by default all tags reserved 99 99 100 100 # Delimiter tags 101 101 self.tags[OPERATION_ATTRIBUTES_TAG] = "operation-attributes-tag" … … 104 104 self.tags[PRINTER_ATTRIBUTES_TAG] = "printer-attributes-tag" 105 105 self.tags[UNSUPPORTED_ATTRIBUTES_TAG] = "unsupported-attributes-tag" 106 106 107 107 # out of band values 108 108 self.tags[0x10] = "unsupported" … … 110 110 self.tags[0x12] = "unknown" 111 111 self.tags[0x13] = "no-value" 112 112 113 113 # integer values 114 114 self.tags[0x20] = "generic-integer" … … 116 116 self.tags[0x22] = "boolean" 117 117 self.tags[0x23] = "enum" 118 118 119 119 # octetString 120 120 self.tags[0x30] = "octetString-with-an-unspecified-format" … … 125 125 self.tags[0x35] = "textWithLanguage" 126 126 self.tags[0x36] = "nameWithLanguage" 127 127 128 128 # character strings 129 129 self.tags[0x20] = "generic-character-string" … … 137 137 self.tags[0x48] = "naturalLanguage" 138 138 self.tags[0x49] = "mimeMediaType" 139 139 140 140 # now parses the IPP message 141 141 self.parse() 142 143 def printInfo(self, msg) : 142 143 def printInfo(self, msg) : 144 144 """Prints a debug message.""" 145 145 if self.debug : 146 146 sys.stderr.write("%s\n" % msg) 147 147 sys.stderr.flush() 148 149 def parseTag(self) : 148 149 def parseTag(self) : 150 150 """Extracts information from an IPP tag.""" 151 151 pos = self.position … … 156 156 if not namelength : 157 157 name = self._curname 158 else : 158 else : 159 159 posend += namelength 160 160 self._curname = name = self.data[pos2:posend] … … 169 169 self.printInfo("%s(%s) %s" % (name, tagtype, value)) 170 170 return posend - self.position 171 172 def operation_attributes_tag(self) : 171 172 def operation_attributes_tag(self) : 173 173 """Indicates that the parser enters into an operation-attributes-tag group.""" 174 174 self.printInfo("Start of operation_attributes_tag") 175 175 self._curdict = self.operation_attributes 176 176 return self.parseTag() 177 178 def job_attributes_tag(self) : 177 178 def job_attributes_tag(self) : 179 179 """Indicates that the parser enters into a job-attributes-tag group.""" 180 180 self.printInfo("Start of job_attributes_tag") 181 181 self._curdict = self.job_attributes 182 182 return self.parseTag() 183 184 def printer_attributes_tag(self) : 183 184 def printer_attributes_tag(self) : 185 185 """Indicates that the parser enters into a printer-attributes-tag group.""" 186 186 self.printInfo("Start of printer_attributes_tag") 187 187 self._curdict = self.printer_attributes 188 188 return self.parseTag() 189 190 def unsupported_attributes_tag(self) : 189 190 def unsupported_attributes_tag(self) : 191 191 """Indicates that the parser enters into an unsupported-attributes-tag group.""" 192 192 self.printInfo("Start of unsupported_attributes_tag") 193 193 self._curdict = self.unsupported_attributes 194 194 return self.parseTag() 195 195 196 196 def parse(self) : 197 197 """Parses an IPP Message. 198 198 199 199 NB : Only a subset of RFC2910 is implemented. 200 200 """ … … 220 220 except IndexError : 221 221 raise IPPError, "Unexpected end of IPP message." 222 222 223 223 # Now transform all one-element lists into single values 224 224 for attrtype in self.attributes_types : … … 227 227 if len(value) == 1 : 228 228 attrdict[key] = value[0] 229 230 class FakeConfig : 229 230 class FakeConfig : 231 231 """Fakes a configuration file parser.""" 232 232 def get(self, section, option, raw=0) : 233 233 """Fakes the retrieval of an option.""" 234 234 raise ConfigError, "Invalid configuration file : no option %s in section [%s]" % (option, section) 235 235 236 236 class CupsBackend : 237 237 """Base class for tools with no database access.""" … … 243 243 self.myname = "tea4cups" 244 244 self.pid = os.getpid() 245 246 def readConfig(self) : 245 246 def readConfig(self) : 247 247 """Reads the configuration file.""" 248 confdir = os.environ.get("CUPS_SERVERROOT", ".") 248 confdir = os.environ.get("CUPS_SERVERROOT", ".") 249 249 self.conffile = os.path.join(confdir, "%s.conf" % self.myname) 250 250 if os.path.isfile(self.conffile) : … … 252 252 self.config.read([self.conffile]) 253 253 self.debug = self.isTrue(self.getGlobalOption("debug", ignore=1)) 254 else : 254 else : 255 255 self.config = FakeConfig() 256 256 self.debug = 1 # no config, so force debug mode ! 257 258 def logInfo(self, message, level="info") : 257 258 def logInfo(self, message, level="info") : 259 259 """Logs a message to CUPS' error_log file.""" 260 260 try : … … 263 263 except IOError : 264 264 pass 265 266 def logDebug(self, message) : 265 266 def logDebug(self, message) : 267 267 """Logs something to debug output if debug is enabled.""" 268 268 if self.debug : 269 269 self.logInfo(message, level="debug") 270 271 def isTrue(self, option) : 270 271 def isTrue(self, option) : 272 272 """Returns 1 if option is set to true, else 0.""" 273 273 if (option is not None) and (option.upper().strip() in ['Y', 'YES', '1', 'ON', 'T', 'TRUE']) : 274 274 return 1 275 else : 275 else : 276 276 return 0 277 278 def getGlobalOption(self, option, ignore=0) : 277 278 def getGlobalOption(self, option, ignore=0) : 279 279 """Returns an option from the global section, or raises a ConfigError if ignore is not set, else returns None.""" 280 280 try : 281 281 return self.config.get("global", option, raw=1) 282 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) : 282 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) : 283 283 if not ignore : 284 284 raise ConfigError, "Option %s not found in section global of %s" % (option, self.conffile) 285 286 def getPrintQueueOption(self, printqueuename, option, ignore=0) : 285 286 def getPrintQueueOption(self, printqueuename, option, ignore=0) : 287 287 """Returns an option from the printer section, or the global section, or raises a ConfigError.""" 288 288 globaloption = self.getGlobalOption(option, ignore=1) 289 289 try : 290 290 return self.config.get(printqueuename, option, raw=1) 291 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) : 291 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) : 292 292 if globaloption is not None : 293 293 return globaloption 294 294 elif not ignore : 295 295 raise ConfigError, "Option %s not found in section [%s] of %s" % (option, printqueuename, self.conffile) 296 296 297 297 def enumBranches(self, printqueuename, branchtype="tee") : 298 298 """Returns the list of branchtypes branches for a particular section's.""" … … 300 300 try : 301 301 globalbranches = [ (k, self.config.get("global", k)) for k in self.config.options("global") if k.startswith(branchbasename) ] 302 except ConfigParser.NoSectionError, msg : 302 except ConfigParser.NoSectionError, msg : 303 303 raise ConfigError, "Invalid configuration file : %s" % msg 304 304 try : 305 305 sectionbranches = [ (k, self.config.get(printqueuename, k)) for k in self.config.options(printqueuename) if k.startswith(branchbasename) ] 306 except ConfigParser.NoSectionError, msg : 306 except ConfigParser.NoSectionError, msg : 307 307 self.logInfo("No section for print queue %s : %s" % (printqueuename, msg)) 308 308 sectionbranches = [] … … 312 312 if value : 313 313 branches[k] = value 314 for (k, v) in sectionbranches : 314 for (k, v) in sectionbranches : 315 315 value = v.strip() 316 316 if value : 317 317 branches[k] = value # overwrite any global option or set a new value 318 else : 318 else : 319 319 del branches[k] # empty value disables a global option 320 320 return branches 321 322 def discoverOtherBackends(self) : 321 322 def discoverOtherBackends(self) : 323 323 """Discovers the other CUPS backends. 324 324 325 325 Executes each existing backend in turn in device enumeration mode. 326 326 Returns the list of available backends. … … 342 342 # see if the pid contained in the lock file is still running 343 343 os.kill(pid, 0) 344 except OSError, e : 344 except OSError, e : 345 345 if e.errno != errno.EPERM : 346 346 # process doesn't exist anymore 347 347 os.remove(lockfilename) 348 348 349 349 if not os.path.exists(lockfilename) : 350 350 lockfile = open(lockfilename, "w") … … 352 352 lockfile.close() 353 353 allbackends = [ os.path.join(directory, b) \ 354 for b in os.listdir(directory) 354 for b in os.listdir(directory) 355 355 if os.access(os.path.join(directory, b), os.X_OK) \ 356 and (b != myname)] 357 for backend in allbackends : 356 and (b != myname)] 357 for backend in allbackends : 358 358 answer = os.popen(backend, "r") 359 359 try : 360 360 devices = [line.strip() for line in answer.readlines()] 361 except : 361 except : 362 362 devices = [] 363 363 status = answer.close() 364 364 if status is None : 365 365 for d in devices : 366 # each line is of the form : 366 # each line is of the form : 367 367 # 'xxxx xxxx "xxxx xxx" "xxxx xxx"' 368 368 # so we have to decompose it carefully … … 381 381 try : 382 382 (devicetype, device, name, fullname) = arguments 383 except ValueError : 383 except ValueError : 384 384 pass # ignore this 'bizarre' device 385 else : 385 else : 386 386 if name.startswith('"') and name.endswith('"') : 387 387 name = name[1:-1] … … 394 394 % (self.myname, self.MyName, self.MyName)) 395 395 return available 396 397 def initBackend(self) : 396 397 def initBackend(self) : 398 398 """Initializes the backend's attributes.""" 399 # check that the DEVICE_URI environment variable's value is 399 # check that the DEVICE_URI environment variable's value is 400 400 # prefixed with self.myname otherwise don't touch it. 401 # If this is the case, we have to remove the prefix from 402 # the environment before launching the real backend 401 # If this is the case, we have to remove the prefix from 402 # the environment before launching the real backend 403 403 muststartwith = "%s:" % self.myname 404 404 device_uri = os.environ.get("DEVICE_URI", "") … … 407 407 device_uri = fulldevice_uri[len(muststartwith):] 408 408 for i in range(2) : 409 if device_uri.startswith("/") : 409 if device_uri.startswith("/") : 410 410 device_uri = device_uri[1:] 411 411 try : 412 (backend, destination) = device_uri.split(":", 1) 413 except ValueError : 412 (backend, destination) = device_uri.split(":", 1) 413 except ValueError : 414 414 if not device_uri : 415 415 self.logDebug("Not attached to an existing print queue.") 416 416 backend = "" 417 else : 417 else : 418 418 raise TeeError, "Invalid DEVICE_URI : %s\n" % device_uri 419 419 420 420 self.JobId = sys.argv[1].strip() 421 421 self.UserName = sys.argv[2].strip() or pwd.getpwuid(os.geteuid())[0] # use CUPS' user when printing test pages from CUPS' web interface … … 425 425 if len(sys.argv) == 7 : 426 426 self.InputFile = sys.argv[6] # read job's datas from file 427 else : 427 else : 428 428 self.InputFile = None # read job's datas from stdin 429 429 430 430 self.RealBackend = backend 431 431 self.DeviceURI = device_uri … … 438 438 ippmessage.job_attributes.get("job-originating-host-name", (None, None))) 439 439 (jbtype, self.JobBilling) = ippmessage.job_attributes.get("job-billing", (None, None)) 440 440 441 441 def getCupsConfigDirectives(self, directives=[]) : 442 442 """Retrieves some CUPS directives from its configuration file. 443 444 Returns a mapping with lowercased directives as keys and 443 444 Returns a mapping with lowercased directives as keys and 445 445 their setting as values. 446 446 """ 447 dirvalues = {} 447 dirvalues = {} 448 448 cupsroot = os.environ.get("CUPS_SERVERROOT", "/etc/cups") 449 449 cupsdconf = os.path.join(cupsroot, "cupsd.conf") 450 450 try : 451 451 conffile = open(cupsdconf, "r") 452 except IOError : 452 except IOError : 453 453 raise TeeError, "Unable to open %s" % cupsdconf 454 else : 454 else : 455 455 for line in conffile.readlines() : 456 456 linecopy = line.strip().lower() … … 459 459 try : 460 460 val = line.split()[1] 461 except : 461 except : 462 462 pass # ignore errors, we take the last value in any case. 463 else : 463 else : 464 464 dirvalues[di] = val 465 conffile.close() 466 return dirvalues 467 468 def parseIPPMessageFile(self) : 465 conffile.close() 466 return dirvalues 467 468 def parseIPPMessageFile(self) : 469 469 """Parses the IPP message file and returns a tuple (filename, parsedvalue).""" 470 470 cupsdconf = self.getCupsConfigDirectives(["RequestRoot"]) … … 472 472 if (len(self.JobId) < 5) and self.JobId.isdigit() : 473 473 ippmessagefile = "c%05i" % int(self.JobId) 474 else : 474 else : 475 475 ippmessagefile = "c%s" % self.JobId 476 476 ippmessagefile = os.path.join(requestroot, ippmessagefile) … … 478 478 try : 479 479 ippdatafile = open(ippmessagefile) 480 except : 480 except : 481 481 self.logInfo("Unable to open IPP message file %s" % ippmessagefile, "warn") 482 else : 482 else : 483 483 self.logDebug("Parsing of IPP message file %s begins." % ippmessagefile) 484 484 try : 485 485 ippmessage = IPPMessage(ippdatafile.read()) 486 except IPPError, msg : 486 except IPPError, msg : 487 487 self.logInfo("Error while parsing %s : %s" % (ippmessagefile, msg), "warn") 488 else : 488 else : 489 489 self.logDebug("Parsing of IPP message file %s ends." % ippmessagefile) 490 490 ippdatafile.close() 491 491 return (ippmessagefile, ippmessage) 492 493 def exportAttributes(self) : 492 493 def exportAttributes(self) : 494 494 """Exports our backend's attributes to the environment.""" 495 495 os.environ["DEVICE_URI"] = self.DeviceURI # WARNING ! … … 508 508 os.environ["TEABILLING"] = self.JobBilling or "" 509 509 os.environ["TEACONTROLFILE"] = self.ControlFile 510 510 511 511 def saveDatasAndCheckSum(self) : 512 512 """Saves the input datas into a static file.""" … … 516 516 infile = open(self.InputFile, "rb") 517 517 mustclose = 1 518 else : 518 else : 519 519 infile = sys.stdin 520 520 CHUNK = 64*1024 # read 64 Kb at a time … … 522 522 sizeread = 0 523 523 checksum = md5.new() 524 outfile = open(self.DataFile, "wb") 524 outfile = open(self.DataFile, "wb") 525 525 while 1 : 526 data = infile.read(CHUNK) 526 data = infile.read(CHUNK) 527 527 if not data : 528 528 break 529 sizeread += len(data) 529 sizeread += len(data) 530 530 outfile.write(data) 531 checksum.update(data) 531 checksum.update(data) 532 532 if not (dummy % 32) : # Only display every 2 Mb 533 533 self.logDebug("%s bytes saved..." % sizeread) 534 dummy += 1 534 dummy += 1 535 535 outfile.close() 536 if mustclose : 536 if mustclose : 537 537 infile.close() 538 self.JobSize = sizeread 538 self.JobSize = sizeread 539 539 self.JobMD5Sum = checksum.hexdigest() 540 540 self.logDebug("Job %s is %s bytes long." % (self.JobId, self.JobSize)) … … 545 545 if not self.isTrue(self.getPrintQueueOption(self.PrinterName, "keepfiles", ignore=1)) : 546 546 os.remove(self.DataFile) 547 547 548 548 def sigtermHandler(self, signum, frame) : 549 549 """Sets an attribute whenever SIGTERM is received.""" 550 550 self.gotSigTerm = 1 551 551 self.logInfo("SIGTERM received for Job %s." % self.JobId) 552 553 def runBranches(self) : 552 553 def runBranches(self) : 554 554 """Launches each hook defined for the current print queue.""" 555 555 self.isCancelled = 0 # did a prehook cancel the print job ? … … 577 577 if not retcode : 578 578 self.logInfo("OK") 579 else : 579 else : 580 580 self.logInfo("An error occured, please check CUPS' error_log file.") 581 581 return retcode 582 582 583 583 def stdioRedirSystem(self, cmd, stdin=0, stdout=1) : 584 584 """Launches a command with stdio redirected.""" … … 601 601 return os.WEXITSTATUS(status) 602 602 return -1 603 603 604 604 def runCommand(self, branch, command) : 605 605 """Runs a particular branch command.""" … … 614 614 return self.stdioRedirSystem(command, self.pipes[bname][0]) 615 615 616 def runCommands(self, btype, branches, serialize) : 616 def runCommands(self, btype, branches, serialize) : 617 617 """Runs the commands for a particular branch type.""" 618 exitcode = 0 618 exitcode = 0 619 619 btype = btype.lower() 620 620 btypetitle = btype.title() 621 branchlist = branches.keys() 621 branchlist = branches.keys() 622 622 branchlist.sort() 623 623 if serialize : … … 628 628 retcode = self.runCommand(branch, branches[branch]) 629 629 self.logDebug("Exit code for %s %s on printer %s is %s" % (btype, branch, self.PrinterName, retcode)) 630 if retcode : 630 if retcode : 631 631 if (btype == "prehook") and (retcode == 255) : # -1 632 632 self.logInfo("Job %s cancelled by prehook %s" % (self.JobId, branch)) 633 633 self.isCancelled = 1 634 else : 634 else : 635 635 self.logInfo("%s %s on printer %s didn't exit successfully." % (btypetitle, branch, self.PrinterName), "error") 636 636 exitcode = 1 637 637 self.logDebug("End serialized %ss" % btypetitle) 638 else : 638 else : 639 639 self.logDebug("Begin forked %ss" % btypetitle) 640 640 pids = {} … … 645 645 if pid : 646 646 pids[branch] = pid 647 else : 647 else : 648 648 os._exit(self.runCommand(branch, branches[branch])) 649 649 for (branch, pid) in pids.items() : … … 654 654 retcode = -1 655 655 self.logDebug("Exit code for %s %s (PID %s) on printer %s is %s" % (btype, branch, pid, self.PrinterName, retcode)) 656 if retcode : 656 if retcode : 657 657 if (btype == "prehook") and (retcode == 255) : # -1 658 658 self.logInfo("Job %s cancelled by prehook %s" % (self.JobId, branch)) 659 659 self.isCancelled = 1 660 else : 660 else : 661 661 self.logInfo("%s %s (PID %s) on printer %s didn't exit successfully." % (btypetitle, branch, pid, self.PrinterName), "error") 662 662 exitcode = 1 663 663 self.logDebug("End forked %ss" % btypetitle) 664 664 return exitcode 665 666 def runOriginalBackend(self) : 665 666 def runOriginalBackend(self) : 667 667 """Launches the original backend.""" 668 668 originalbackend = os.path.join(os.path.split(sys.argv[0])[0], self.RealBackend) … … 695 695 self.logInfo("CUPS backend %s returned %d." % (originalbackend, status), "error") 696 696 return status 697 elif not killed : 697 elif not killed : 698 698 self.logInfo("CUPS backend %s died abnormally." % originalbackend, "error") 699 699 return -1 700 else : 700 else : 701 701 return 1 702 703 if __name__ == "__main__" : 702 703 if __name__ == "__main__" : 704 704 # This is a CUPS backend, we should act and die like a CUPS backend 705 705 wrapper = CupsBackend() 706 706 if len(sys.argv) == 1 : 707 707 print "\n".join(wrapper.discoverOtherBackends()) 708 sys.exit(0) 709 elif len(sys.argv) not in (6, 7) : 708 sys.exit(0) 709 elif len(sys.argv) not in (6, 7) : 710 710 sys.stderr.write("ERROR: %s job-id user title copies options [file]\n"\ 711 711 % sys.argv[0]) 712 712 sys.exit(1) 713 else : 713 else : 714 714 try : 715 715 wrapper.readConfig() … … 719 719 retcode = wrapper.runBranches() 720 720 wrapper.cleanUp() 721 except SystemExit, e : 721 except SystemExit, e : 722 722 retcode = e.code 723 except : 723 except : 724 724 import traceback 725 725 lines = []