source: trunk/debathena/config/printing-config/files/usr/bin/lpr.debathena @ 24384

Revision 24384, 9.9 KB checked in by geofft, 14 years ago (diff)
In printing-config: * Allow the IPP URL's path component to start with /classes/ in addition to /printers/, since duplex queues are implemented as classes, so that we deal with Kerberized duplex queues correctly (see [help.mit.edu #1163697]).
  • Property svn:executable set to *
RevLine 
[23055]1#!/usr/bin/python
2
3# lpr.debathena
4#
5# also lp.debathena, lpq.debathena, and lprm.debathena
6#
7# Wrapper script that intelligently determines whether a command was
8# intended for should go to CUPS or LPRng and sends it off in the
9# right direction
10
[24201]11import cups
[23157]12import errno
13import hesiod
[23055]14import getopt
15import os
[23950]16import shlex
[24201]17import socket
[23055]18import sys
19from subprocess import call, PIPE
[24201]20import urllib
[23055]21
[24201]22try:
23    cupsd = cups.Connection()
24except RuntimeError: # Is this actually safer than "except:"?
25    cupsd = None
26
27def hesiod_lookup(hes_name, hes_type):
[24209]28    """A wrapper with somewhat graceful error handling."""
[24201]29    try:
30        h = hesiod.Lookup(hes_name, hes_type)
31        if len(h.results) > 0:
32            return h.results
[24265]33    except IOError:
34        return []
[24201]35
36cups_frontends = ['cups.mit.edu',
37                  'printers.mit.edu',
38                  'cluster-printers.mit.edu']
39cups_backends = [s.lower() for s in
40                 hesiod_lookup('cups-print', 'sloc')
41               + hesiod_lookup('cups-cluster', 'sloc')]
42
43
[23950]44def lpropt_transform(args):
45    if 'LPROPT' in os.environ:
[24001]46        return shlex.split(os.environ['LPROPT']) + args
[23950]47    return args
48
[23945]49def zephyr_transform(options):
[23954]50    zephyr = True
51    return_options = []
52    for o, a in options:
[24216]53        if o == '-N':
54            zephyr = False
[23954]55        else:
56            return_options.append((o, a))
[23945]57
[23954]58    if zephyr and os.environ.get('ATHENA_USER'):
59        return_options.append(('-m', 'zephyr%' + os.environ['ATHENA_USER']))
[23945]60
[23954]61    return return_options
62
[23055]63opts = {
[23847]64    'cups': {
[24201]65        'lp': ('EU:cd:h:mn:o:q:st:H:P:i:', None, '-d'),
66        'lpq': ('EU:h:P:al', None, '-P'),
[24374]67        'lpr': ('EH:U:P:#:hlmo:pqrC:J:T:',
68            (lpropt_transform, zephyr_transform), '-P'),
[24201]69        'lprm': ('EU:h:P:', None, '-P'),
[23847]70    },
71    'lprng': {
[24201]72        'lp': ('ckmprswBGYd:D:f:n:q:t:', None, '-d'),
73        'lpq': ('aAlLVcvP:st:D:', None, '-P'),
[24216]74        'lpr': ('ABblC:D:F:Ghi:kJ:K:#:m:NP:rR:sT:U:Vw:X:YZ:z1:2:3:4:',
[24201]75            (lpropt_transform, zephyr_transform), '-P'),
76        'lprm': ('aAD:P:VU:', None, '-P'),
[23847]77    }
[23055]78}
79
[23847]80preflist = {
81    'cups': ['cups', 'lprng'],
82    'lprng': ['lprng', 'cups']
83}
84
[23055]85def error(code, message):
86    """Exit out with an error
87    """
88    sys.stderr.write(message)
89    sys.exit(code)
90
[24201]91def execCups(command, queue, args):
92    """Pass the command and arguments on to the CUPS versions of the command
[23055]93    """
[24201]94    new_command = '/usr/bin/cups-%s' % command
95    os.execv(new_command, [command] + args)
[23055]96
[24201]97def execLprng(command, queue, args):
98    """Pass the command and arguments on to the LPRng versions of the command
[23055]99    """
[24201]100    new_command = '/usr/bin/mit-%s' % command
101    os.execv(new_command, [command] + args)
[23055]102
[24201]103def getPrintQueue(command, args):
[23055]104    """Given argv, extract the printer name, using knowledge of the possible
105    command line options for both the CUPS and LPRng versions of the command
[23847]106    that this script was called as. Also, return whether the command line
107    options imply a particular version of the command.
[23055]108    """
[24201]109
110    preference = 'cups'
111    for version in ['cups', 'lprng']:
[23847]112        try:
113            # Get the set of options that correspond to the command that this
114            # script was invoked as
[24201]115            (cmd_opts, transformers, destopt) = opts[version][command]
[23950]116            if transformers:
117                (transform_args, transform_opts) = transformers
118            else:
[24001]119                transform_args = transform_opts = lambda x: x
[23847]120        except KeyError:
121            error(1, """
122Error: this script was called as %s, when it must be called as
[24202]123one of lpr, lpq, lprm, or lp
[23847]124
125""" % command)
126
127        # Attempt to parse it with the current version of this command
[24001]128        targs = transform_args(args)
[23847]129        try:
[24001]130            options, realargs = getopt.gnu_getopt(targs, cmd_opts)
[23847]131        except getopt.GetoptError:
132            # That's the wrong version, so try the next one.
133            continue
134
[24001]135        options = transform_opts(options)
136        ttargs = [o + a for o, a in options] + realargs
[24201]137        if destopt:
138            for o, a in options:
139                if o == destopt:
140                    return (version, a, ttargs)
141        else:
142            if len(realargs) > 0:
143                return (version, realargs[0], ttargs)
[23847]144        # Since we've successfully getopt'd, don't try any other versions,
145        # but do note that we like this version.
146        preference = version
147        break
148
[24201]149    # Either we failed to getopt, or nobody told us what printer to use,
150    # so let's use the default printer
[23157]151    default = os.getenv('PRINTER')
[24201]152    if not default:
153        if cupsd:
154            default = cupsd.getDefault()
155    if not default:
156        h = hesiod_lookup(os.uname()[1], 'cluster')
157        for result in h:
[24227]158            (key, value) = result.split(None, 1)
[24201]159            if key == 'lpr':
160                default = value
161
[23157]162    if default:
[23945]163        return (preference, default, args)
[24201]164    else:
165        error(2, """
166No default printer configured. Specify a -P option, or configure a
167default printer via e.g. System | Administration | Printing.
168
169""")
170
171def getCupsURI(printer):
172    if not cupsd:
173        return None
[23157]174    try:
[24201]175        attrs = cupsd.getPrinterAttributes(printer)
176        return attrs.get('device-uri')
177    except cups.IPPError:
178        return None
179
180def translate_lprng_args_to_cups(command, args):
181    # TODO yell at user if/when we decide that these args are deprecated
182
183    # If getopt fails, something went very wrong -- _we_ generated this
184    options, realargs = getopt.gnu_getopt(args, opts['lprng'][command][0])
185    cupsargs = []
186    for (o, a) in options:
187        if o in ('-a') and command == 'lpq':
188            cupsargs += [('-a', a)]
189        elif o in ('-b', '-l') and command == 'lpr':
190            cupsargs += [('-l', a)]
191        elif o in ('-h') and command == 'lpr':
192            cupsargs += [('-h', a)]
193        elif o in ('-J') and command == 'lpr':
194            cupsargs += [('-J', a)]
195        elif o in ('-K', '-#') and command == 'lpr':
196            cupsargs += [('-#', a)]
197        elif o in ('-P'):
198            cupsargs += [('-P', a)]
199        elif o in ('-T') and command == 'lpr':
200            cupsargs += [('-T', a)]
201        elif o in ('-U') and command == 'lpr':
202            cupsargs += [('-U', a)]
203        elif o in ('-v') and command == 'lpq':
204            cupsargs += [('-l', a)]
205        elif o in ('-Z') and command == 'lpr':
206            if a == 'simplex':
207                cupsargs += [('-o', 'sides=one-sided')]
208            elif a == 'duplex':
209                cupsargs += [('-o', 'sides=two-sided-long-edge')]
210            elif a == 'duplexshort':
211                cupsargs += [('-o', 'sides=two-sided-short-edge')]
212            # TODO attempt to deal banner=staff
213        elif o in ('-m') and command == 'lpr':
214            # TODO figure out if CUPS can do mail/zephyr
215            pass # Don't warn about this, we probably generated it
[23157]216        else:
[24201]217            sys.stderr.write("Warning: option %s%s not converted to CUPS\n"
218                             % (o, a))
219    joincupsargs = [o + a for o, a in cupsargs] + realargs
220    sys.stderr.write("Using cups-%s %s\n" % (command, ' '.join(joincupsargs)))
221    return joincupsargs
[23055]222
223if __name__ == '__main__':
224    # Remove the command name from the arguments when we extract it
225    command = os.path.basename(sys.argv.pop(0))
[23847]226
227    # Determine if the arguments prefer one version of this command,
[24201]228    # For instance, "lpr -Zduplex" wants mit-lpr.
229    (preference, queue, args) = getPrintQueue(command, sys.argv)
[23847]230
[24201]231    # Figure out if the queue exists on the local cupsd, and if so, if
232    # it's an Athena queue.
233    # Note that the "local" cupsd might not be on localhost; client.conf or
234    # CUPS_SERVER might have it pointing to e.g. cups.mit.edu, but that's okay.
235    url = getCupsURI(queue)
236    if url:
[24209]237        proto = rest = hostport = path = host = port = None
238
[24201]239        (proto, rest) = urllib.splittype(url)
[24209]240        if rest:
241            (hostport, path) = urllib.splithost(rest)
242        if hostport:
243            (host, port) = urllib.splitport(hostport)
244        if (proto and host and path and
245            proto == 'ipp' and
[24384]246            host.lower() in cups_frontends + cups_backends):
[24201]247            # Canonicalize the queue name to Athena's, in case someone
248            # has a local printer called something memorable like 'w20'
249            # that points to 'ajax' or something
[24384]250            if path[0:10] == '/printers/':
251                queue = path[10:]
252            elif path[0:9] == '/classes/':
253                queue = path[9:]
254            else: # we can't parse CUPS' URL, punt to CUPS
255                execCups(command, queue, args)
[24201]256        else:
257            execCups(command, queue, args)
258
259    # Get rid of any instance on the queue name
260    # TODO The purpose of instances is to have different sets of default
261    # options. Queues may also have default options on the null
262    # instance. Figure out if we need to do anything about them
263    queue = queue.split('/')[0]
264
265    # If we're still here, the queue is definitely an Athena print
266    # queue; it was either in the local cupsd pointing to Athena, or the
267    # local cupsd didn't know about it.
268    # Figure out what Athena thinks the backend server is, and whether
269    # that server is running a cupsd; if not, fall back to LPRng
270
271    pcap = hesiod_lookup(queue, 'pcap')
272    rm = None
273    if pcap:
274        for field in pcap[0].split(':'):
275            if field[0:3] == 'rm=':
276                rm = field[3:]
277    if not rm:
278        # In the unlikely event we're wrong about it being an Athena
279        # print queue, the local cupsd is good enough
280        execCups(command, queue, args)
281
282    try:
283        # See if rm is running a cupsd. If not, assume it's an LPRng server.
284        s = socket.socket()
285        s.settimeout(0.3)
286        s.connect((rm, 631))
287        s.close()
288    except (socket.error, socket.timeout):
289        execLprng(command, queue, args)
290
291    os.environ['CUPS_SERVER'] = rm
292    if preference == 'lprng':
293        args = translate_lprng_args_to_cups(command, args)
294    execCups(command, queue, args)
Note: See TracBrowser for help on using the repository browser.