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

Revision 24374, 9.7 KB checked in by jdreed, 14 years ago (diff)
In printing-config: [ Geoffrey Thomas ] [ Jonathan Reed ] * Add lpropt_transform and zephyr_transform for cups invocation of lpr, so that LPROPT is respected when the command-line arguments are subsystem-agnostic (Trac #509)
  • 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:',
68            (lpropt_transform, zephyr_transform), '-P'),
69        'lprm': ('EU:h:P:', None, '-P'),
70    },
71    'lprng': {
72        'lp': ('ckmprswBGYd:D:f:n:q:t:', None, '-d'),
73        'lpq': ('aAlLVcvP:st:D:', None, '-P'),
74        'lpr': ('ABblC:D:F:Ghi:kJ:K:#:m:NP:rR:sT:U:Vw:X:YZ:z1:2:3:4:',
75            (lpropt_transform, zephyr_transform), '-P'),
76        'lprm': ('aAD:P:VU:', None, '-P'),
77    }
78}
79
80preflist = {
81    'cups': ['cups', 'lprng'],
82    'lprng': ['lprng', 'cups']
83}
84
85def error(code, message):
86    """Exit out with an error
87    """
88    sys.stderr.write(message)
89    sys.exit(code)
90
91def execCups(command, queue, args):
92    """Pass the command and arguments on to the CUPS versions of the command
93    """
94    new_command = '/usr/bin/cups-%s' % command
95    os.execv(new_command, [command] + args)
96
97def execLprng(command, queue, args):
98    """Pass the command and arguments on to the LPRng versions of the command
99    """
100    new_command = '/usr/bin/mit-%s' % command
101    os.execv(new_command, [command] + args)
102
103def getPrintQueue(command, args):
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
106    that this script was called as. Also, return whether the command line
107    options imply a particular version of the command.
108    """
109
110    preference = 'cups'
111    for version in ['cups', 'lprng']:
112        try:
113            # Get the set of options that correspond to the command that this
114            # script was invoked as
115            (cmd_opts, transformers, destopt) = opts[version][command]
116            if transformers:
117                (transform_args, transform_opts) = transformers
118            else:
119                transform_args = transform_opts = lambda x: x
120        except KeyError:
121            error(1, """
122Error: this script was called as %s, when it must be called as
123one of lpr, lpq, lprm, or lp
124
125""" % command)
126
127        # Attempt to parse it with the current version of this command
128        targs = transform_args(args)
129        try:
130            options, realargs = getopt.gnu_getopt(targs, cmd_opts)
131        except getopt.GetoptError:
132            # That's the wrong version, so try the next one.
133            continue
134
135        options = transform_opts(options)
136        ttargs = [o + a for o, a in options] + realargs
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)
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
149    # Either we failed to getopt, or nobody told us what printer to use,
150    # so let's use the default printer
151    default = os.getenv('PRINTER')
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:
158            (key, value) = result.split(None, 1)
159            if key == 'lpr':
160                default = value
161
162    if default:
163        return (preference, default, args)
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
174    try:
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
216        else:
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
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))
226
227    # Determine if the arguments prefer one version of this command,
228    # For instance, "lpr -Zduplex" wants mit-lpr.
229    (preference, queue, args) = getPrintQueue(command, sys.argv)
230
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:
237        proto = rest = hostport = path = host = port = None
238
239        (proto, rest) = urllib.splittype(url)
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
246            host.lower() in cups_frontends + cups_backends and
247            path[0:10] == '/printers/'):
248            # Canonicalize the queue name to Athena's, in case someone
249            # has a local printer called something memorable like 'w20'
250            # that points to 'ajax' or something
251            queue = path[10:]
252        else:
253            execCups(command, queue, args)
254
255    # Get rid of any instance on the queue name
256    # TODO The purpose of instances is to have different sets of default
257    # options. Queues may also have default options on the null
258    # instance. Figure out if we need to do anything about them
259    queue = queue.split('/')[0]
260
261    # If we're still here, the queue is definitely an Athena print
262    # queue; it was either in the local cupsd pointing to Athena, or the
263    # local cupsd didn't know about it.
264    # Figure out what Athena thinks the backend server is, and whether
265    # that server is running a cupsd; if not, fall back to LPRng
266
267    pcap = hesiod_lookup(queue, 'pcap')
268    rm = None
269    if pcap:
270        for field in pcap[0].split(':'):
271            if field[0:3] == 'rm=':
272                rm = field[3:]
273    if not rm:
274        # In the unlikely event we're wrong about it being an Athena
275        # print queue, the local cupsd is good enough
276        execCups(command, queue, args)
277
278    try:
279        # See if rm is running a cupsd. If not, assume it's an LPRng server.
280        s = socket.socket()
281        s.settimeout(0.3)
282        s.connect((rm, 631))
283        s.close()
284    except (socket.error, socket.timeout):
285        execLprng(command, queue, args)
286
287    os.environ['CUPS_SERVER'] = rm
288    if preference == 'lprng':
289        args = translate_lprng_args_to_cups(command, args)
290    execCups(command, queue, args)
Note: See TracBrowser for help on using the repository browser.