root / pykota / trunk / bin / cupspykota @ 1514

Revision 1514, 28.1 kB (checked in by jalet, 20 years ago)

Moved the sigterm capturing elsewhere

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