Changeset 1271 for pykota/trunk/bin
- Timestamp:
- 01/12/04 00:22:42 (20 years ago)
- Location:
- pykota/trunk/bin
- Files:
-
- 2 modified
Legend:
- Unmodified
- Added
- Removed
-
pykota/trunk/bin/cupspykota
r1257 r1271 24 24 # 25 25 # $Log$ 26 # Revision 1.20 2004/01/11 23:22:42 jalet 27 # Major code refactoring, it's way cleaner, and now allows automated addition 28 # of printers on first print. 29 # 26 30 # Revision 1.19 2004/01/08 14:10:32 jalet 27 31 # Copyright year changed. … … 108 112 from pykota.requester import PyKotaRequesterError 109 113 114 gotSigTerm = 0 115 def sigterm_handler(signum, frame) : 116 """Sets a global variable whenever SIGTERM is received.""" 117 # SIGTERM will be ignore most of the time, but during 118 # the call to the real backend, we have to pass it through. 119 global gotSigTerm 120 gotSigTerm = 1 121 110 122 class PyKotaPopen3(popen2.Popen3) : 111 123 """Our own class to execute real backends. … … 129 141 os._exit(1) 130 142 131 gotSigTerm = 0 132 def sigterm_handler(signum, frame) : 133 """Sets a global variable whenever SIGTERM is received.""" 134 # SIGTERM will be ignore most of the time, but during 135 # the call to the real backend, we have to pass it through. 136 global gotSigTerm 137 gotSigTerm = 1 138 139 def main(thebackend) : 140 """Do it, and do it right !""" 141 # first deal with signals 142 # CUPS backends ignore SIGPIPE and exit(1) on SIGTERM 143 # Fortunately SIGPIPE is already ignored by Python 144 # It's there just in case this changes in the future. 145 # Here we have to handle SIGTERM correctly, and pass 146 # it to the original backend if needed. 147 global gotSigTerm 148 gotSigTerm = 0 149 signal.signal(signal.SIGPIPE, signal.SIG_IGN) 150 signal.signal(signal.SIGTERM, sigterm_handler) 151 152 # 153 # retrieve some informations on the current printer and user 154 # from the Quota Storage. 155 printer = thebackend.storage.getPrinter(thebackend.printername) 156 if not printer.Exists : 157 # The printer is unknown from the Quota Storage perspective 158 # we send an error back to CUPS, which will stop 159 # the print queue. 160 thebackend.logger.log_message(_("Printer %s not registered in the PyKota system") % thebackend.printername, "error") 161 return 1 162 else : 163 for dummy in range(2) : 164 user = thebackend.storage.getUser(thebackend.username) 165 if user.Exists : 166 break 167 else : 168 # The user is unknown from the Quota Storage perspective 169 # Depending on the default policy for this printer, we 170 # either let the job pass through or reject it, but we 171 # log a message in any case. 172 (policy, args) = thebackend.config.getPrinterPolicy(thebackend.printername) 173 if policy == "ALLOW" : 174 action = "POLICY_ALLOW" 175 elif policy == "EXTERNAL" : 176 commandline = thebackend.formatCommandLine(args, user, printer) 177 thebackend.logger.log_message(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (thebackend.username, commandline, thebackend.printername), "info") 178 if os.system(commandline) : 179 # if an error occured, we die without error, 180 # so that the job doesn't stop the print queue. 181 thebackend.logger.log_message(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, thebackend.printername), "error") 182 return 0 183 else : 184 # here we try a second time, because the goal 185 # of the external action was to add the user 186 # in the database. 187 continue 188 else : 189 action = "POLICY_DENY" 190 thebackend.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thebackend.username, action, thebackend.printername), "warn") 191 if action == "POLICY_DENY" : 192 # if not allowed to print then die, else proceed. 193 # we die without error, so that the job doesn't 194 # stop the print queue. 195 return 0 196 # when we get there, the printer policy allows the job to pass 197 break 143 class PyKotaBackend(PyKotaFilterOrBackend) : 144 """A class for the pykota backend.""" 145 def acceptJob(self) : 146 """Returns the appropriate exit code to tell CUPS all is OK.""" 147 return 0 148 149 def removeJob(self) : 150 """Returns the appropriate exit code to let CUPS think all is OK. 151 152 Returning 0 (success) prevents CUPS from stopping the print queue. 153 """ 154 return 0 155 156 def doWork(self, policy, printer, user, userpquota) : 157 """Most of the work is done here.""" 158 global gotSigTerm 159 # Two different values possible for policy here : 160 # ALLOW means : Either printer, user or user print quota doesn't exist, 161 # but the job should be allowed anyway. 162 # OK means : Both printer, user and user print quota exist, job should 163 # be allowed if current user is allowed to print on this printer 164 if policy == "OK" : 165 self.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name)) 166 action = self.warnUserPQuota(userpquota) 167 if action not in ["ALLOW", "WARN"] : 168 return self.removeJob() 169 self.logdebug("Job accounting begins.") 170 self.accounter.beginJob(userpquota) 171 172 # pass the job's data to the real backend 173 if gotSigTerm : 174 retcode = self.removeJob() 175 else : 176 retcode = self.handleData() 177 178 if policy == "OK" : 179 # stops accounting. 180 self.accounter.endJob(userpquota) 181 self.logdebug("Job accounting ends.") 182 183 # retrieve the job size 184 jobsize = self.accounter.getJobSize() 185 self.logdebug("Job size : %i" % jobsize) 186 187 # update the quota for the current user on this printer 188 jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0) 189 if jobsize : 190 self.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name)) 191 userpquota.increasePagesUsage(jobsize) 192 193 # adds the current job to history 194 printer.addJobToHistory(self.jobid, user, self.accounter.getLastPageCounter(), action, jobsize, jobprice, self.preserveinputfile, self.title, self.copies, self.options) 195 self.logdebug("Job added to history.") 196 197 return retcode 198 198 199 if user.Exists : 200 # Is the current user allowed to print at all ? 201 thebackend.logdebug("Checking user %s's quota on printer %s" % (user.Name, printer.Name)) 202 action = thebackend.warnUserPQuota(thebackend.storage.getUserPQuota(user, printer)) 203 elif policy == "EXTERNAL" : 204 # if the extenal policy produced no error, but the 205 # user still doesn't exist, we die without error, 206 # so that the job doesn't stop the print queue. 207 thebackend.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thebackend.printername, thebackend.username), "error") 208 return 0 209 210 if action not in ["ALLOW", "WARN"] : 211 # if not allowed to print then die, else proceed. 212 # we die without error, so that the job doesn't 213 # stop the print queue. 214 retcode = 0 215 else : 216 # pass the job untouched to the underlying layer 217 # and starts accounting at the same time 218 thebackend.logdebug("Job accounting begins.") 219 thebackend.accounter.beginJob(printer, user) 220 199 def handleData(self) : 200 """Pass the job's data to the real backend.""" 221 201 # Now it becomes tricky... 202 # We must pass the unmodified job to the original backend 203 204 global gotSigTerm 222 205 223 206 # First ensure that we have a file object as input 224 207 mustclose = 0 225 if thebackend.inputfile is not None :226 if hasattr( thebackend.inputfile, "read") :227 infile = thebackend.inputfile208 if self.inputfile is not None : 209 if hasattr(self.inputfile, "read") : 210 infile = self.inputfile 228 211 else : 229 infile = open( thebackend.inputfile, "rb")212 infile = open(self.inputfile, "rb") 230 213 mustclose = 1 231 214 else : … … 233 216 234 217 # Find the real backend pathname 235 realbackend = os.path.join(os.path.split(sys.argv[0])[0], thebackend.originalbackend)218 realbackend = os.path.join(os.path.split(sys.argv[0])[0], self.originalbackend) 236 219 237 220 # And launch it 238 thebackend.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])])))221 self.logdebug("Starting real backend %s with args %s" % (realbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + sys.argv[1:])]))) 239 222 subprocess = PyKotaPopen3([realbackend] + sys.argv[1:], capturestderr=1, bufsize=0, arg0=os.environ["DEVICE_URI"]) 240 223 … … 264 247 status = -1 265 248 inputclosed = 0 266 thebackend.logdebug("Entering streams polling loop...")249 self.logdebug("Entering streams polling loop...") 267 250 while status == -1 : 268 251 # First check if original backend is still alive … … 280 263 availablefds = pollster.poll() 281 264 for (fd, mask) in availablefds : 282 # thebackend.logdebug("file: %i mask: %04x" % (fd, mask))265 # self.logdebug("file: %i mask: %04x" % (fd, mask)) 283 266 if mask & select.POLLOUT : 284 267 # We can write … … 326 309 pollster.unregister(fromcfno) 327 310 os.close(fromcfno) 328 thebackend.logdebug("Real backend's stdout ends.")311 self.logdebug("Real backend's stdout ends.") 329 312 elif fd == cerrfno : 330 313 # Original CUPS backend has finished … … 337 320 pollster.unregister(cerrfno) 338 321 os.close(cerrfno) 339 thebackend.logdebug("Real backend's stderr ends.")322 self.logdebug("Real backend's stderr ends.") 340 323 341 324 if endinput and not inputclosed : … … 351 334 os.close(tocfno) 352 335 inputclosed = 1 353 thebackend.logdebug("Input data ends.")336 self.logdebug("Input data ends.") 354 337 355 338 # Input file was a real file, we have to close it. … … 357 340 infile.close() 358 341 359 thebackend.logdebug("Exiting streams polling loop...")342 self.logdebug("Exiting streams polling loop...") 360 343 361 344 # Check exit code of original CUPS backend. … … 363 346 retcode = os.WEXITSTATUS(status) 364 347 else : 365 thebackend.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error")348 self.logger.log_message(_("CUPS backend %s died abnormally.") % realbackend, "error") 366 349 retcode = -1 367 368 # stops accounting. 369 thebackend.accounter.endJob(printer, user) 370 thebackend.logdebug("Job accounting ends.") 371 372 # retrieve the job size 373 jobsize = thebackend.accounter.getJobSize() 374 thebackend.logdebug("Job size : %i" % jobsize) 375 376 # update the quota for the current user on this printer 377 jobprice = (float(printer.PricePerPage or 0.0) * jobsize) + float(printer.PricePerJob or 0.0) 378 if jobsize : 379 userquota = thebackend.storage.getUserPQuota(user, printer) 380 if userquota.Exists : 381 thebackend.logdebug("Updating user %s's quota on printer %s" % (user.Name, printer.Name)) 382 userquota.increasePagesUsage(jobsize) 383 384 # adds the current job to history 385 printer.addJobToHistory(thebackend.jobid, user, thebackend.accounter.getLastPageCounter(), action, jobsize, jobprice, thebackend.preserveinputfile, thebackend.title, thebackend.copies, thebackend.options) 386 thebackend.logdebug("Job added to history.") 387 388 return retcode # return (retcode or gotSigTerm) shouldn't be needed 389 350 return retcode 351 390 352 if __name__ == "__main__" : 391 353 # This is a CUPS backend, we should act and die like a CUPS backend 354 355 # first deal with signals 356 # CUPS backends ignore SIGPIPE and exit(1) on SIGTERM 357 # Fortunately SIGPIPE is already ignored by Python 358 # It's there just in case this changes in the future. 359 # Here we have to handle SIGTERM correctly, and pass 360 # it to the original backend if needed. 361 global gotSigTerm 362 gotSigTerm = 0 363 signal.signal(signal.SIGPIPE, signal.SIG_IGN) 364 signal.signal(signal.SIGTERM, sigterm_handler) 365 392 366 if len(sys.argv) == 1 : 393 367 # we will execute each existing backend in device enumeration mode … … 433 407 try : 434 408 # Initializes the backend 435 kotabackend = PyKota FilterOrBackend()436 retcode = main(kotabackend)409 kotabackend = PyKotaBackend() 410 retcode = kotabackend.mainWork() 437 411 except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, PyKotaRequesterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg : 438 412 sys.stderr.write("ERROR : cupspykota backend failed (%s)\n" % msg) -
pykota/trunk/bin/pykota
r1257 r1271 24 24 # 25 25 # $Log$ 26 # Revision 1.50 2004/01/11 23:22:42 jalet 27 # Major code refactoring, it's way cleaner, and now allows automated addition 28 # of printers on first print. 29 # 26 30 # Revision 1.49 2004/01/08 14:10:32 jalet 27 31 # Copyright year changed. … … 228 232 # UNKNOWN 229 233 return -1 234 235 def doWork(self, policy, printer, user, userpquota) : 236 """Most of the work is done here.""" 237 # Two different values possible for policy here : 238 # ALLOW means : Either printer, user or user print quota doesn't exist, 239 # but the job should be allowed anyway. 240 # OK means : Both printer, user and user print quota exist, job should 241 # be allowed if current user is allowed to print on this printer 242 if policy == "OK" : 243 self.logdebug("Does accounting for user %s on printer %s." % (user.Name, printer.Name)) 244 action = self.accounter.doAccounting(userpquota) 245 if action == "DENY" : 246 # Just die. 247 self.logdebug("Printing is denied.") 248 return self.removeJob() 230 249 231 def main(thefilter) : 232 """Do it, and do it right !""" 233 # 234 # If this is a CUPS filter, we should act and die like a CUPS filter when needed 235 if thefilter.printingsystem == "CUPS" : 236 if len(sys.argv) not in (6, 7) : 237 sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0]) 238 return thefilter.removeJob() 239 240 # Get the last page counter and last username from the Quota Storage backend 241 printer = thefilter.storage.getPrinter(thefilter.printername) 242 if not printer.Exists : 243 # The printer is unknown from the Quota Storage perspective. 244 # we just cancel the job. 245 thefilter.logger.log_message(_("Printer %s not registered in the PyKota system") % thefilter.printername, "error") 246 return thefilter.removeJob() 247 else : 248 for dummy in range(2) : 249 user = thefilter.storage.getUser(thefilter.username) 250 if user.Exists : 251 break 250 # pass the job's data to the next filter 251 self.logdebug("Passing input data to next filter.") 252 mustclose = 0 253 if self.inputfile is not None : 254 if hasattr(self.inputfile, "read") : 255 infile = self.inputfile 252 256 else : 253 # The user is unknown from the Quota Storage perspective 254 # Depending on the default policy for this printer, we 255 # either let the job pass through or reject it, but we 256 # log a message in any case. 257 (policy, args) = thefilter.config.getPrinterPolicy(thefilter.printername) 258 if policy == "ALLOW" : 259 action = "POLICY_ALLOW" 260 elif policy == "EXTERNAL" : 261 commandline = thefilter.formatCommandLine(args, user, printer) 262 thefilter.logger.log_message(_("User %s not registered in the PyKota system, applying external policy (%s) for printer %s") % (thefilter.username, commandline, thefilter.printername), "info") 263 if os.system(commandline) : 264 thefilter.logger.log_message(_("External policy %s for printer %s produced an error. Job rejected. Please check PyKota's configuration files.") % (commandline, thefilter.printername), "error") 265 return thefilter.removeJob() 266 else : 267 # here we try a second time, because the goal 268 # of the external action was to add the user 269 # in the database. 270 continue 271 else : 272 action = "POLICY_DENY" 273 thefilter.logger.log_message(_("User %s not registered in the PyKota system, applying default policy (%s) for printer %s") % (thefilter.username, action, thefilter.printername), "warn") 274 if action == "POLICY_DENY" : 275 return thefilter.removeJob() 276 # when we get there, the printer policy allows the job to pass 277 break 257 infile = open(self.inputfile, "rb") 258 mustclose = 1 259 else : 260 infile = sys.stdin 261 data = infile.read(256*1024) 262 while data : 263 sys.stdout.write(data) 264 data = infile.read(256*1024) 265 if mustclose : 266 infile.close() 267 268 return self.acceptJob() 269 270 def mainWork(self) : 271 # If this is a CUPS filter, we should act and die like a CUPS filter when needed 272 if self.printingsystem == "CUPS" : 273 if len(sys.argv) not in (6, 7) : 274 sys.stderr.write("ERROR: %s job-id user title copies options [file]\n" % sys.argv[0]) 275 return self.removeJob() 276 277 if self.accounter.isDelayed : 278 # Here we need to update the last user's print quota 279 # because hardware accounting is delayed by one print job 280 # in the pykota filter. 281 printer = self.storage.getPrinter(self.printername) 282 if not printer.Exists : 283 self.logger.log_message(_("Printer %s not registered in the PyKota system") % self.printername, "info") 284 else : 285 if not printer.LastJob.Exists : 286 self.logger.log_message(_("Printer %s was never used") % self.printername, "info") 287 else : 288 self.logdebug("Updating print quota for last user %s on printer %s" % (printer.LastJob.User.Name, printer.Name)) 289 lastuserpquota = self.storage.getUserPQuota(printer.LastJob.User, printer) 290 self.accounter.counterbefore = printer.LastJob.PrinterPageCounter 291 self.accounter.counterafter = self.accounter.getPrinterInternalPageCounter() or 0 292 lastjobsize = self.accounter.getJobSize() 293 self.logdebug("Last Job size : %i" % lastjobsize) 294 printer.LastJob.setSize(lastjobsize) 295 self.logdebug("Updating user %s's quota on printer %s" % (lastuserpquota.User.Name, printer.Name)) 296 lastuserpquota.increasePagesUsage(lastjobsize) 297 self.warnUserPQuota(lastuserpquota) 278 298 279 # if user exists, do accounting 280 if user.Exists : 281 # Now does the accounting and act depending on the result 282 thefilter.logdebug("Does accounting for user %s on printer %s." % (user.Name, printer.Name)) 283 action = thefilter.accounter.doAccounting(printer, user) 284 285 # if not allowed to print then die, else proceed. 286 if action == "DENY" : 287 # No, just die cleanly 288 thefilter.logdebug("Printing is denied.") 289 return thefilter.removeJob() 290 elif policy == "EXTERNAL" : 291 thefilter.logger.log_message(_("External policy %s for printer %s couldn't add user %s. Job rejected.") % (commandline, thefilter.printername, thefilter.username), "error") 292 return thefilter.removeJob() 293 294 # pass the job untouched to the underlying layer 295 thefilter.logdebug("Passing input data to next filter.") 296 thefilter.accounter.filterInput(thefilter.inputfile) 297 298 return thefilter.acceptJob() 299 299 # then deal with current print job as usual 300 PyKotaFilterOrBackend.mainWork(self) 301 300 302 if __name__ == "__main__" : 301 303 retcode = -1 … … 303 305 # Initializes the current tool 304 306 kotafilter = PyKotaFilter() 305 retcode = main(kotafilter)307 retcode = kotafilter.mainWork() 306 308 except (PyKotaToolError, PyKotaConfigError, PyKotaStorageError, PyKotaAccounterError, PyKotaRequesterError, AttributeError, KeyError, IndexError, ValueError, TypeError, IOError), msg : 307 309 sys.stderr.write("ERROR : PyKota filter failed (%s)\n" % msg)