root / pykota / trunk / bin / cupspykota @ 1515

Revision 1515, 28.2 kB (checked in by jalet, 20 years ago)

I think the bug when cancelling jobs should be fixed right now

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# CUPSPyKota accounting backend
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003-2004 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22#
23# $Id$
24#
25# $Log$
26# Revision 1.57  2004/06/02 22:18:07  jalet
27# I think the bug when cancelling jobs should be fixed right now
28#
29# Revision 1.56  2004/06/02 21:50:56  jalet
30# Moved the sigterm capturing elsewhere
31#
32# Revision 1.55  2004/06/02 14:25:07  jalet
33# Should correctly capture ALL errors now
34#
35# Revision 1.54  2004/05/26 16:44:48  jalet
36# Now logs something when client hostname can't be extracted
37#
38# Revision 1.53  2004/05/26 14:49:35  jalet
39# First try at saving the job-originating-hostname in the database
40#
41# Revision 1.52  2004/05/25 09:15:13  jalet
42# accounter.py : old code deleted
43# the rest : now exports PYKOTAPRECOMPUTEDJOBSIZE and PYKOTAPRECOMPUTEDJOBPRICE
44#
45# Revision 1.51  2004/05/25 08:31:16  jalet
46# Heavy CPU usage seems to be fixed at least !
47#
48# Revision 1.50  2004/05/25 05:17:50  jalet
49# Now precomputes the job's size only if current printer's enforcement
50# is "STRICT"
51#
52# Revision 1.49  2004/05/24 22:45:48  jalet
53# New 'enforcement' directive added
54# Polling loop improvements
55#
56# Revision 1.48  2004/05/24 14:36:24  jalet
57# Revert to old polling loop. Will need optimisations
58#
59# Revision 1.47  2004/05/24 11:59:46  jalet
60# More robust (?) code
61#
62# Revision 1.46  2004/05/21 22:02:51  jalet
63# Preliminary work on pre-accounting
64#
65# Revision 1.45  2004/05/19 07:15:32  jalet
66# Could the 'misterious' bug in my loop be finally fixed ???
67#
68# Revision 1.44  2004/05/18 14:48:47  jalet
69# Big code changes to completely remove the need for "requester" directives,
70# jsut use "hardware(... your previous requester directive's content ...)"
71#
72# Revision 1.43  2004/05/17 11:46:05  jalet
73# First try at cupspykota's main loop rewrite
74#
75# Revision 1.42  2004/05/10 11:22:28  jalet
76# Typo
77#
78# Revision 1.41  2004/05/10 10:07:30  jalet
79# Catches OSError while reading
80#
81# Revision 1.40  2004/05/10 09:29:48  jalet
82# Should be more robust if we receive a SIGTERM during an I/O operation
83#
84# Revision 1.39  2004/05/07 14:44:53  jalet
85# Fix for file handles unregistered twice from the polling object
86#
87# Revision 1.38  2004/04/09 22:24:46  jalet
88# Began work on correct handling of child processes when jobs are cancelled by
89# the user. Especially important when an external requester is running for a
90# long time.
91#
92# Revision 1.37  2004/03/18 19:11:25  jalet
93# Fix for raw jobs in cupspykota
94#
95# Revision 1.36  2004/03/18 14:03:18  jalet
96# Added fsync() calls
97#
98# Revision 1.35  2004/03/16 12:05:01  jalet
99# Small fix for new waitprinter.sh : when job was denied, would wait forever
100# for printer being in printing mode.
101#
102# Revision 1.34  2004/03/15 10:47:56  jalet
103# This time the traceback formatting should be correct !
104#
105# Revision 1.33  2004/03/05 12:46:07  jalet
106# Improve tracebacks
107#
108# Revision 1.32  2004/03/05 12:31:35  jalet
109# Now should output full traceback when crashing
110#
111# Revision 1.31  2004/03/01 14:35:56  jalet
112# PYKOTAPHASE wasn't set soon enough at the start of the job
113#
114# Revision 1.30  2004/03/01 14:34:15  jalet
115# PYKOTAPHASE wasn't set at the right time at the end of data transmission
116# to underlying layer (real backend)
117#
118# Revision 1.29  2004/03/01 11:23:25  jalet
119# Pre and Post hooks to external commands are available in the cupspykota
120# backend. Forthe pykota filter they will be implemented real soon now.
121#
122# Revision 1.28  2004/02/26 14:18:07  jalet
123# Should fix the remaining bugs wrt printers groups and users groups.
124#
125# Revision 1.27  2004/02/04 23:41:27  jalet
126# Should fix the incorrect "backend died abnormally" problem.
127#
128# Revision 1.26  2004/01/30 16:35:03  jalet
129# Fixes stupid software accounting bug in CUPS backend
130#
131# Revision 1.25  2004/01/16 17:51:46  jalet
132# Fuck Fuck Fuck !!!
133#
134# Revision 1.24  2004/01/14 15:52:01  jalet
135# Small fix for job cancelling code.
136#
137# Revision 1.23  2004/01/13 10:48:28  jalet
138# Small streams polling loop modification.
139#
140# Revision 1.22  2004/01/12 22:43:40  jalet
141# New formula to compute a job's price
142#
143# Revision 1.21  2004/01/12 18:17:36  jalet
144# Denied jobs weren't stored into the history anymore, this is now fixed.
145#
146# Revision 1.20  2004/01/11 23:22:42  jalet
147# Major code refactoring, it's way cleaner, and now allows automated addition
148# of printers on first print.
149#
150# Revision 1.19  2004/01/08 14:10:32  jalet
151# Copyright year changed.
152#
153# Revision 1.18  2004/01/07 16:16:32  jalet
154# Better debugging information
155#
156# Revision 1.17  2003/12/27 16:49:25  uid67467
157# Should be ok now.
158#
159# Revision 1.17  2003/12/06 08:54:29  jalet
160# Code simplifications.
161# Added many debugging messages.
162#
163# Revision 1.16  2003/11/26 20:43:29  jalet
164# Inadvertantly introduced a bug, which is fixed.
165#
166# Revision 1.15  2003/11/26 19:17:35  jalet
167# Printing on a printer not present in the Quota Storage now results
168# in the job being stopped or cancelled depending on the system.
169#
170# Revision 1.14  2003/11/25 13:25:45  jalet
171# Boolean problem with old Python, replaced with 0
172#
173# Revision 1.13  2003/11/23 19:01:35  jalet
174# Job price added to history
175#
176# Revision 1.12  2003/11/21 14:28:43  jalet
177# More complete job history.
178#
179# Revision 1.11  2003/11/19 23:19:35  jalet
180# Code refactoring work.
181# Explicit redirection to /dev/null has to be set in external policy now, just
182# like in external mailto.
183#
184# Revision 1.10  2003/11/18 17:54:24  jalet
185# SIGTERMs are now transmitted to original backends.
186#
187# Revision 1.9  2003/11/18 14:11:07  jalet
188# Small fix for bizarre urls
189#
190# Revision 1.8  2003/11/15 14:26:44  jalet
191# General improvements to the documentation.
192# Email address changed in sample configuration file, because
193# I receive low quota messages almost every day...
194#
195# Revision 1.7  2003/11/14 22:05:12  jalet
196# New CUPS backend fully functionnal.
197# Old CUPS configuration method is now officially deprecated.
198#
199# Revision 1.6  2003/11/14 20:13:11  jalet
200# We exit the loop too soon.
201#
202# Revision 1.5  2003/11/14 18:31:27  jalet
203# Not perfect, but seems to work with the poll() loop.
204#
205# Revision 1.4  2003/11/14 17:04:15  jalet
206# More (untested) work on the CUPS backend.
207#
208# Revision 1.3  2003/11/12 23:27:44  jalet
209# More work on new backend. This commit may be unstable.
210#
211# Revision 1.2  2003/11/12 09:33:34  jalet
212# New CUPS backend supports device enumeration
213#
214# Revision 1.1  2003/11/08 16:05:31  jalet
215# CUPS backend added for people to experiment.
216#
217#
218#
219
220import sys
221import os
222import fcntl
223import popen2
224import cStringIO
225import shlex
226import select
227import signal
228import time
229
230from pykota.tool import PyKotaFilterOrBackend, PyKotaToolError
231from pykota.config import PyKotaConfigError
232from pykota.storage import PyKotaStorageError
233from pykota.accounter import PyKotaAccounterError
234   
235class PyKotaPopen4(popen2.Popen4) :
236    """Our own class to execute real backends.
237   
238       Their first argument is different from their path so using
239       native popen2.Popen3 would not be feasible.
240    """
241    def __init__(self, cmd, bufsize=-1, arg0=None) :
242        self.arg0 = arg0
243        popen2.Popen4.__init__(self, cmd, bufsize)
244       
245    def _run_child(self, cmd):
246        for i in range(3, 256): # TODO : MAXFD in original popen2 module
247            try:
248                os.close(i)
249            except OSError:
250                pass
251        try:
252            os.execvpe(cmd[0], [self.arg0 or cmd[0]] + cmd[1:], os.environ)
253        finally:
254            os._exit(1)
255   
256class PyKotaBackend(PyKotaFilterOrBackend) :       
257    """A class for the pykota backend."""
258    def acceptJob(self) :       
259        """Returns the appropriate exit code to tell CUPS all is OK."""
260        return 0
261           
262    def removeJob(self) :           
263        """Returns the appropriate exit code to let CUPS think all is OK.
264       
265           Returning 0 (success) prevents CUPS from stopping the print queue.
266        """   
267        return 0
268       
269    def getPageLogLocation(self) :
270        """Retrieves CUPS' page_log file location."""
271        location = None
272        cupsroot = os.environ.get("CUPS_SERVERROOT", "/etc/cups")
273        cupsdconf = os.path.join(cupsroot, "cupsd.conf")
274        try :
275            conffile = open(cupsdconf, "r")
276        except IOError :   
277            self.logdebug("Unable to open %s" % cupsdconf)
278            return # file doesn't exist or can't be read
279        else :   
280            for line in conffile.readlines() :
281                linecopy = line.strip().lower()
282                if linecopy.startswith("pagelog ") :
283                    try :
284                        location = line.split()[1]
285                    except :   
286                        pass # ignore errors, we take the last value in any case.
287            conffile.close()           
288            return location           
289           
290    def getJobOriginatingHostname(self, printername, username, jobid) :
291        """Retrieves the job-originating-hostname from the CUPS page_log file if possible."""
292        pagelogpath = self.getPageLogLocation() or "/var/log/cups/page_log"
293        try :
294            pagelog = open(pagelogpath, "r")
295        except IOError :   
296            self.logdebug("Unable to open %s" % pagelogpath)
297            return # no page log or can't read it, originating hostname unknown yet
298        else :   
299            # TODO : read backward so we could take first value seen
300            # TODO : here we read forward so we must take the last value seen
301            prefix = "%s %s %s" % (printername, username, jobid)
302            matchingline = None
303            while 1 :
304                line = pagelog.readline()
305                if not line :
306                    break
307                else :
308                    line = line.strip()
309                    if line.startswith(prefix) :   
310                        matchingline = line # no break, because we read forward
311            pagelog.close()       
312            if matchingline is None :
313                self.logdebug("No matching line found in page_log")
314                return # correct line not found, job-originating-hostname unknown
315            else :   
316                return matchingline.split()[-1]
317               
318    def doWork(self, policy, printer, user, userpquota) :   
319        """Most of the work is done here."""
320        # Two different values possible for policy here :
321        # ALLOW means : Either printer, user or user print quota doesn't exist,
322        #               but the job should be allowed anyway.
323        # OK means : Both printer, user and user print quota exist, job should
324        #            be allowed if current user is allowed to print on this printer
325        if policy == "OK" :
326            # exports user information with initial values
327            self.exportUserInfo(userpquota)
328           
329            # tries to extract job-originating-hostname
330            clienthost = self.getJobOriginatingHostname(printer.Name, user.Name, self.jobid)
331            self.logdebug("Client Hostname : %s" % (clienthost or "Unknown"))   
332            os.putenv("PYKOTAJOBORIGINATINGHOSTNAME", str(clienthost or ""))
333           
334            # enters first phase
335            os.putenv("PYKOTAPHASE", "BEFORE")
336           
337            # checks the user's quota
338            if self.config.getPrinterEnforcement(printer.Name) == "STRICT" :
339                self.softwareJobSize = self.precomputeJobSize()
340                self.softwareJobPrice = userpquota.computeJobPrice(self.softwareJobSize)
341                self.logdebug("Precomputed job's size is %s pages, price is %s units" % (self.softwareJobSize, self.softwareJobPrice))
342            os.putenv("PYKOTAPRECOMPUTEDJOBSIZE", str(self.softwareJobSize))   
343            os.putenv("PYKOTAPRECOMPUTEDJOBPRICE", str(self.softwareJobPrice))   
344            action = self.warnUserPQuota(userpquota)
345           
346            # exports some new environment variables
347            os.putenv("PYKOTAACTION", action)
348           
349            # launches the pre hook
350            self.prehook(userpquota)
351           
352            self.logdebug("Job accounting begins.")
353            self.accounter.beginJob(userpquota)
354        else :   
355            action = "ALLOW"
356            os.putenv("PYKOTAACTION", action)
357           
358        # pass the job's data to the real backend   
359        if action in ["ALLOW", "WARN"] :
360            if self.gotSigTerm :
361                retcode = self.removeJob()
362            else :   
363                retcode = self.handleData()       
364        else :       
365            retcode = self.removeJob()
366       
367        if policy == "OK" :       
368            # indicate phase change
369            os.putenv("PYKOTAPHASE", "AFTER")
370           
371            # stops accounting.
372            self.accounter.endJob(userpquota)
373            self.logdebug("Job accounting ends.")
374               
375            # retrieve the job size   
376            if action == "DENY" :
377                jobsize = 0
378                self.logdebug("Job size forced to 0 because printing is denied.")
379            else :   
380                jobsize = self.accounter.getJobSize()
381            self.logdebug("Job size : %i" % jobsize)
382           
383            # update the quota for the current user on this printer
384            self.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name))
385            jobprice = userpquota.increasePagesUsage(jobsize)
386           
387            # adds the current job to history   
388            printer.addJobToHistory(self.jobid, user, self.accounter.getLastPageCounter(), action, jobsize, jobprice, self.preserveinputfile, self.title, self.copies, self.options, clienthost)
389            self.logdebug("Job added to history.")
390           
391            # exports some new environment variables
392            os.putenv("PYKOTAJOBSIZE", str(jobsize))
393            os.putenv("PYKOTAJOBPRICE", str(jobprice))
394           
395            # then re-export user information with new values
396            self.exportUserInfo(userpquota)
397           
398            # Launches the post hook
399            self.posthook(userpquota)
400           
401        return retcode   
402               
403    def unregisterFileNo(self, pollobj, fileno) :               
404        """Removes a file handle from the polling object."""
405        try :
406            pollobj.unregister(fileno)
407        except KeyError :   
408            self.logger.log_message(_("File number %s unregistered twice from polling object, ignored.") % fileno, "warn")
409        except :   
410            self.logdebug("Error while unregistering file number %s from polling object." % fileno)
411        else :   
412            self.logdebug("File number %s unregistered from polling object." % fileno)
413           
414    def formatFileEvent(self, fd, mask) :       
415        """Formats file debug info."""
416        maskval = []
417        if mask & select.POLLIN :
418            maskval.append("POLLIN")
419        if mask & select.POLLOUT :
420            maskval.append("POLLOUT")
421        if mask & select.POLLPRI :
422            maskval.append("POLLPRI")
423        if mask & select.POLLERR :
424            maskval.append("POLLERR")
425        if mask & select.POLLHUP :
426            maskval.append("POLLHUP")
427        if mask & select.POLLNVAL :
428            maskval.append("POLLNVAL")
429        return "%s (%s)" % (fd, " | ".join(maskval))
430       
431    def handleData(self) :                   
432        """Pass the job's data to the real backend."""
433        # Find the real backend pathname   
434        realbackend = os.path.join(os.path.split(sys.argv[0])[0], self.originalbackend)
435       
436        # And launch it
437        self.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])])))
438        subprocess = PyKotaPopen4([realbackend] + sys.argv[1:], bufsize=0, arg0=os.environ["DEVICE_URI"])
439       
440        # Save file descriptors, we will need them later.
441        stderrfno = sys.stderr.fileno()
442        fromcfno = subprocess.fromchild.fileno()
443        tocfno = subprocess.tochild.fileno()
444       
445        # We will have to be careful when dealing with I/O
446        # So we use a poll object to know when to read or write
447        pollster = select.poll()
448        pollster.register(fromcfno, select.POLLIN | select.POLLPRI)
449        pollster.register(stderrfno, select.POLLOUT)
450        pollster.register(tocfno, select.POLLOUT)
451       
452        # Initialize our buffers
453        indata = ""
454        outdata = ""
455        endinput = endoutput = 0
456        inputclosed = outputclosed = 0
457       
458        if self.preserveinputfile is None :
459            # this is not a real file, we read the job's data
460            # from our temporary file which is a copy of stdin
461            infno = self.jobdatastream.fileno()
462            self.jobdatastream.seek(0)
463            pollster.register(infno, select.POLLIN | select.POLLPRI)
464        else :   
465            # job's data is in a file, no need to pass the data
466            # to the real backend
467            self.logdebug("Job's data is in %s" % self.preserveinputfile)
468            infno = None
469            endinput = 1
470       
471        self.logdebug("Entering streams polling loop...")
472        MEGABYTE = 1024*1024
473        killed = 0
474        status = -1
475        while (status == -1) and (not killed) and not (inputclosed and outputclosed) :
476            # First check if original backend is still alive
477            status = subprocess.poll()
478           
479            # Now if we got SIGTERM, we have
480            # to kill -TERM the original backend
481            if self.gotSigTerm and not killed :
482                try :
483                    os.kill(subprocess.pid, signal.SIGTERM)
484                except OSError, msg : # ignore but logs if process was already killed.
485                    self.logdebug("Error while sending signal to pid %s : %s" % (subprocess.pid, msg))
486                else :   
487                    self.logger.log_message(_("SIGTERM was sent to real backend %s (pid: %s)") % (realbackend, subprocess.pid), "info")
488                    killed = 1
489           
490            # In any case, deal with any remaining I/O
491            try :
492                availablefds = pollster.poll(5000)
493            except select.error, msg :   
494                self.logdebug("Interrupted poll : %s" % msg)
495                availablefds = []
496            if not availablefds :
497                self.logdebug("Nothing to do, sleeping a bit...")
498                time.sleep(0.01) # give some time to the system
499            else :
500                for (fd, mask) in availablefds :
501                    # self.logdebug(self.formatFileEvent(fd, mask))
502                    try :
503                        if mask & select.POLLOUT :
504                            # We can write
505                            if fd == tocfno :
506                                if indata :
507                                    try :
508                                        os.write(fd, indata)   
509                                    except (OSError, IOError), msg :   
510                                        self.logdebug("Error while writing to real backend's stdin %s : %s" % (fd, msg))
511                                    else :   
512                                        indata = ""
513                                else :       
514                                    self.logdebug("No data to send to real backend yet, sleeping a bit...")
515                                    time.sleep(0.01)
516                                   
517                                if endinput :   
518                                    self.unregisterFileNo(pollster, tocfno)       
519                                    self.logdebug("Closing real backend's stdin.")
520                                    os.close(tocfno)
521                                    inputclosed = 1
522                            elif fd == stderrfno :
523                                if outdata :
524                                    try :
525                                        os.write(fd, outdata)
526                                    except (OSError, IOError), msg :   
527                                        self.logdebug("Error while writing to CUPS back channel (stderr) %s : %s" % (fd, msg))
528                                    else :
529                                        outdata = ""
530                                else :       
531                                    # self.logdebug("No data to send back to CUPS yet, sleeping a bit...") # Uncommenting this fills your logs
532                                    time.sleep(0.01) # Give some time to the system, stderr is ALWAYS writeable it seems.
533                                   
534                                if endoutput :   
535                                    self.unregisterFileNo(pollster, stderrfno)       
536                                    outputclosed = 1
537                            else :   
538                                self.logdebug("Unexpected : %s - Sleeping a bit..." % self.formatFileEvent(fd, mask))
539                                time.sleep(0.01)
540                               
541                        if mask & (select.POLLIN | select.POLLPRI) :     
542                            # We have something to read
543                            try :
544                                data = os.read(fd, MEGABYTE)
545                            except (IOError, OSError), msg :   
546                                self.logdebug("Error while reading file %s : %s" % (fd, msg))
547                            else :
548                                if fd == infno :
549                                    if not data :    # If yes, then no more input data
550                                        self.unregisterFileNo(pollster, infno)
551                                        self.logdebug("Input data ends.")
552                                        endinput = 1 # this happens with real files.
553                                    else :   
554                                        indata += data
555                                elif fd == fromcfno :
556                                    if not data :
557                                        self.logdebug("No back channel data to read from real backend yet, sleeping a bit...")
558                                        time.sleep(0.01)
559                                    else :
560                                        outdata += data
561                                else :   
562                                    self.logdebug("Unexpected : %s - Sleeping a bit..." % self.formatFileEvent(fd, mask))
563                                    time.sleep(0.01)
564                                   
565                        if mask & (select.POLLHUP | select.POLLERR) :
566                            # Treat POLLERR as an EOF.
567                            # Some standard I/O stream has no more datas
568                            self.unregisterFileNo(pollster, fd)
569                            if fd == infno :
570                                # Here we are in the case where the input file is stdin.
571                                # which has no more data to be read.
572                                self.logdebug("Input data ends.")
573                                endinput = 1
574                            elif fd == fromcfno :   
575                                # We are no more interested in this file descriptor       
576                                self.logdebug("Closing real backend's stdout+stderr.")
577                                os.close(fromcfno)
578                                endoutput = 1
579                            else :   
580                                self.logdebug("Unexpected : %s - Sleeping a bit..." % self.formatFileEvent(fd, mask))
581                                time.sleep(0.01)
582                               
583                        if mask & select.POLLNVAL :       
584                            self.logdebug("File %s was closed. Unregistering from polling object." % fd)
585                            self.unregisterFileNo(pollster, fd)
586                    except IOError, msg :           
587                        self.logdebug("Got an IOError : %s" % msg) # we got signalled during an I/O
588               
589        # We must close the real backend's input stream
590        if killed and not inputclosed :
591            self.logdebug("Forcing close of real backend's stdin.")
592            os.close(tocfno)
593       
594        self.logdebug("Exiting streams polling loop...")
595       
596        # Check exit code of original CUPS backend.   
597        if status == -1 :
598            # we exited the loop before the real backend exited
599            # now we have to wait for it to finish and get its status
600            self.logdebug("Waiting for real backend to exit...")
601            try :
602                status = subprocess.wait()
603            except OSError : # already dead   
604                status = 0
605        if os.WIFEXITED(status) :
606            retcode = os.WEXITSTATUS(status)
607        elif not killed :   
608            self.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error")
609            retcode = -1
610        else :   
611            retcode = self.removeJob()
612        return retcode   
613   
614if __name__ == "__main__" :   
615    # This is a CUPS backend, we should act and die like a CUPS backend
616    if len(sys.argv) == 1 :
617        # we will execute each existing backend in device enumeration mode
618        # and generate their PyKota accounting counterpart
619        (directory, myname) = os.path.split(sys.argv[0])
620        for backend in [os.path.join(directory, b) for b in os.listdir(directory) if os.path.isfile(os.path.join(directory, b)) and (b != myname)] :
621            answer = os.popen(backend, "r")
622            try :
623                devices = [line.strip() for line in answer.readlines()]
624            except :   
625                devices = []
626            status = answer.close()
627            if status is None :
628                for d in devices :
629                    # each line is of the form : 'xxxx xxxx "xxxx xxx" "xxxx xxx"'
630                    # so we have to decompose it carefully
631                    fdevice = cStringIO.StringIO("%s" % d)
632                    tokenizer = shlex.shlex(fdevice)
633                    tokenizer.wordchars = tokenizer.wordchars + r".:,?!~/\_$*-+={}[]()#"
634                    arguments = []
635                    while 1 :
636                        token = tokenizer.get_token()
637                        if token :
638                            arguments.append(token)
639                        else :
640                            break
641                    fdevice.close()
642                    try :
643                        (devicetype, device, name, fullname) = arguments
644                    except ValueError :   
645                        pass    # ignore this 'bizarre' device
646                    else :   
647                        if name.startswith('"') and name.endswith('"') :
648                            name = name[1:-1]
649                        if fullname.startswith('"') and fullname.endswith('"') :
650                            fullname = fullname[1:-1]
651                        print '%s cupspykota:%s "PyKota+%s" "PyKota managed %s"' % (devicetype, device, name, fullname)
652        retcode = 0
653    elif len(sys.argv) not in (6, 7) :   
654        sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0])
655        retcode = 1
656    else :   
657        try :
658            # Initializes the backend
659            kotabackend = PyKotaBackend()   
660            retcode = kotabackend.mainWork()
661        except :
662            import traceback
663            lines = []
664            for line in traceback.format_exception(*sys.exc_info()) :
665                lines.extend([l for l in line.split("\n") if l])
666            sys.stderr.write("ERROR : cupspykota backend failed : \n%s" % (msg, "ERROR : ".join(["%s\n" % l for l in lines])))
667            sys.stderr.flush()
668            retcode = 1
669       
670        try :
671            kotabackend.storage.close()
672        except (TypeError, NameError, AttributeError) :   
673            pass
674           
675        kotabackend.closeJobDataStream()   
676       
677    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.