root / pykota / trunk / bin / cupspykota @ 1502

Revision 1502, 27.9 kB (checked in by jalet, 20 years ago)

First try at saving the job-originating-hostname in the database

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