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

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