root / pykota / trunk / bin / cupspykota @ 1517

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

Improved error logging.
crashrecipient directive added.
Now exports the job's size in bytes too.

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